Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-19 18:08:23 +00:00
parent 4a72126942
commit 3f0db3db2a
91 changed files with 576 additions and 617 deletions

View File

@ -236,6 +236,7 @@ Dangerfile @gl-quality/eng-prod
/ee/lib/gitlab/ci/reports/dependency_list/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/reports/license_scanning/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/reports/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be
/ee/app/services/ci/run_dast_scan_service.rb @gitlab-org/secure/dynamic-analysis-be
[Container Security]
/ee/app/views/projects/threat_monitoring/** @gitlab-org/protect/container-security-frontend

View File

@ -39,6 +39,15 @@ compile-production-assets:
- public/assets/
- webpack-report/
when: always
before_script:
- if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then
echo "Checking out \$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ($CI_MERGE_REQUEST_SOURCE_BRANCH_SHA) instead of \$CI_COMMIT_SHA (merge result commit $CI_COMMIT_SHA) so that GitLab assets image tag actually reflect the commit for which assets were compiled.";
git checkout -f ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA};
else
echo "Building the image from \$CI_COMMIT_SHA ($CI_COMMIT_SHA) for this non-merge result pipeline.";
fi;
- echo "See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/index.html#with-pipeline-for-merged-results for more details.";
- !reference [.default-before_script, before_script]
after_script:
- rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here

View File

@ -5,6 +5,7 @@ import {
GlDropdownItem,
GlDropdownSectionHeader,
GlDropdownDivider,
GlTooltipDirective,
} from '@gitlab/ui';
import { convertArrayToCamelCase } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
@ -21,6 +22,9 @@ export default {
GlDropdownDivider,
...Actions,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
user: {
type: Object,
@ -30,6 +34,11 @@ export default {
type: Object,
required: true,
},
showButtonLabels: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
userActions() {
@ -56,6 +65,13 @@ export default {
userPaths() {
return generateUserPaths(this.paths, this.user.username);
},
editButtonAttrs() {
return {
'data-testid': 'edit',
icon: 'pencil-square',
href: this.userPaths.edit,
};
},
},
methods: {
isLdapAction(action) {
@ -70,51 +86,68 @@ export default {
</script>
<template>
<div class="gl-display-flex gl-justify-content-end" :data-testid="`user-actions-${user.id}`">
<gl-button v-if="hasEditAction" data-testid="edit" :href="userPaths.edit">{{
$options.i18n.edit
}}</gl-button>
<div
class="gl-display-flex gl-justify-content-end gl-my-n2 gl-mx-n2"
:data-testid="`user-actions-${user.id}`"
>
<div v-if="hasEditAction" class="gl-p-2">
<gl-button v-if="showButtonLabels" v-bind="editButtonAttrs">{{
$options.i18n.edit
}}</gl-button>
<gl-button
v-else
v-gl-tooltip="$options.i18n.edit"
v-bind="editButtonAttrs"
:aria-label="$options.i18n.edit"
/>
</div>
<gl-dropdown
v-if="hasDropdownActions"
data-testid="dropdown-toggle"
right
class="gl-ml-2"
icon="settings"
>
<gl-dropdown-section-header>{{ $options.i18n.settings }}</gl-dropdown-section-header>
<div v-if="hasDropdownActions" class="gl-p-2">
<gl-dropdown
data-testid="dropdown-toggle"
right
:text="$options.i18n.userAdministration"
:text-sr-only="!showButtonLabels"
icon="settings"
data-qa-selector="user_actions_dropdown_toggle"
:data-qa-username="user.username"
>
<gl-dropdown-section-header>{{
$options.i18n.userAdministration
}}</gl-dropdown-section-header>
<template v-for="action in dropdownSafeActions">
<component
:is="getActionComponent(action)"
v-if="getActionComponent(action)"
:key="action"
:path="userPaths[action]"
:username="user.name"
:data-testid="action"
>
{{ $options.i18n[action] }}
</component>
<gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action">
{{ $options.i18n[action] }}
</gl-dropdown-item>
</template>
<template v-for="action in dropdownSafeActions">
<component
:is="getActionComponent(action)"
v-if="getActionComponent(action)"
:key="action"
:path="userPaths[action]"
:username="user.name"
:data-testid="action"
>
{{ $options.i18n[action] }}
</component>
<gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action">
{{ $options.i18n[action] }}
</gl-dropdown-item>
</template>
<gl-dropdown-divider v-if="hasDeleteActions" />
<gl-dropdown-divider v-if="hasDeleteActions" />
<template v-for="action in dropdownDeleteActions">
<component
:is="getActionComponent(action)"
v-if="getActionComponent(action)"
:key="action"
:paths="userPaths"
:username="user.name"
:oncall-schedules="user.oncallSchedules"
:data-testid="`delete-${action}`"
>
{{ $options.i18n[action] }}
</component>
</template>
</gl-dropdown>
<template v-for="action in dropdownDeleteActions">
<component
:is="getActionComponent(action)"
v-if="getActionComponent(action)"
:key="action"
:paths="userPaths"
:username="user.name"
:oncall-schedules="user.oncallSchedules"
:data-testid="`delete-${action}`"
>
{{ $options.i18n[action] }}
</component>
</template>
</gl-dropdown>
</div>
</div>
</template>

View File

@ -6,7 +6,7 @@ export const LENGTH_OF_USER_NOTE_TOOLTIP = 100;
export const I18N_USER_ACTIONS = {
edit: __('Edit'),
settings: __('Settings'),
userAdministration: s__('AdminUsers|User administration'),
unlock: __('Unlock'),
block: s__('AdminUsers|Block'),
unblock: s__('AdminUsers|Unblock'),

View File

@ -5,6 +5,7 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import AdminUsersApp from './components/app.vue';
import ModalManager from './components/modals/user_modal_manager.vue';
import UserActions from './components/user_actions.vue';
import {
CONFIRM_DELETE_BUTTON_SELECTOR,
MODAL_TEXTS_CONTAINER_SELECTOR,
@ -17,26 +18,33 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) => {
const initApp = (el, component, userPropKey, props = {}) => {
if (!el) {
return false;
}
const { users, paths } = el.dataset;
const { [userPropKey]: user, paths } = el.dataset;
return new Vue({
el,
apolloProvider,
render: (createElement) =>
createElement(AdminUsersApp, {
createElement(component, {
props: {
users: convertObjectPropsToCamelCase(JSON.parse(users), { deep: true }),
[userPropKey]: convertObjectPropsToCamelCase(JSON.parse(user), { deep: true }),
paths: convertObjectPropsToCamelCase(JSON.parse(paths)),
...props,
},
}),
});
};
export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) =>
initApp(el, AdminUsersApp, 'users');
export const initAdminUserActions = (el = document.querySelector('#js-admin-user-actions')) =>
initApp(el, UserActions, 'user', { showButtonLabels: true });
export const initDeleteUserModals = () => {
const modalsMountElement = document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR);

View File

@ -1,3 +1,6 @@
import { initAdminUserActions, initDeleteUserModals } from '~/admin/users';
import initConfirmModal from '~/confirm_modal';
initAdminUserActions();
initDeleteUserModals();
initConfirmModal();

View File

@ -1,5 +1,8 @@
import { initExpiresAtField } from '~/access_tokens';
import { initAdminUserActions, initDeleteUserModals } from '~/admin/users';
import initConfirmModal from '~/confirm_modal';
initAdminUserActions();
initDeleteUserModals();
initExpiresAtField();
initConfirmModal();

View File

@ -1,6 +1,7 @@
import { initAdminUsersApp, initDeleteUserModals } from '~/admin/users';
import { initAdminUsersApp, initDeleteUserModals, initAdminUserActions } from '~/admin/users';
import initConfirmModal from '~/confirm_modal';
initAdminUsersApp();
initAdminUserActions();
initDeleteUserModals();
initConfirmModal();

View File

@ -1,4 +1,4 @@
import { isEqual, memoize, uniqWith } from 'lodash';
import { memoize } from 'lodash';
import { createSankey } from './dag/drawing_utils';
/*
@ -113,11 +113,24 @@ export const filterByAncestors = (links, nodeDict) =>
return !allAncestors.includes(source);
});
/*
A peformant alternative to lodash's isEqual. Because findIndex always finds
the first instance of a match, if the found index is not the first, we know
it is in fact a duplicate.
*/
const deduplicate = (item, itemIndex, arr) => {
const foundIdx = arr.findIndex((test) => {
return test.source === item.source && test.target === item.target;
});
return foundIdx === itemIndex;
};
export const parseData = (nodes) => {
const nodeDict = createNodeDict(nodes);
const allLinks = makeLinksFromNodes(nodes, nodeDict);
const filteredLinks = filterByAncestors(allLinks, nodeDict);
const links = uniqWith(filteredLinks, isEqual);
const links = filteredLinks.filter(deduplicate);
return { nodes, links };
};

View File

@ -83,7 +83,10 @@ class MembersFinder
union = Gitlab::SQL::Union.new(union_members, remove_duplicates: false) # rubocop: disable Gitlab/Union
sql = distinct_on(union)
Member.includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord
# enumerate the columns here since we are enumerating them in the union and want to be immune to
# column caching issues when adding/removing columns
Member.select(*Member.column_names)
.includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord
end
def distinct_on(union)

View File

@ -123,114 +123,10 @@ module UsersHelper
!user.confirmed?
end
def user_block_data(user, message)
{
path: block_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Block user %{username}?') % { username: sanitize_name(user.name) },
messageHtml: message,
okVariant: 'warning',
okTitle: s_('AdminUsers|Block')
}.to_json
}
end
def user_unblock_data(user)
{
path: unblock_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Unblock user %{username}?') % { username: sanitize_name(user.name) },
message: s_('AdminUsers|You can always block their account again if needed.'),
okVariant: 'info',
okTitle: s_('AdminUsers|Unblock')
}.to_json
}
end
def user_block_effects
header = tag.p s_('AdminUsers|Blocking user has the following effects:')
list = tag.ul do
concat tag.li s_('AdminUsers|User will not be able to login')
concat tag.li s_('AdminUsers|User will not be able to access git repositories')
concat tag.li s_('AdminUsers|Personal projects will be left')
concat tag.li s_('AdminUsers|Owned groups will be left')
end
header + list
end
def user_ban_data(user)
{
path: ban_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Ban user %{username}?') % { username: sanitize_name(user.name) },
message: s_('AdminUsers|You can unban their account in the future. Their data remains intact.'),
okVariant: 'warning',
okTitle: s_('AdminUsers|Ban')
}.to_json
}
end
def user_unban_data(user)
{
path: unban_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Unban %{username}?') % { username: sanitize_name(user.name) },
message: s_('AdminUsers|You ban their account in the future if necessary.'),
okVariant: 'info',
okTitle: s_('AdminUsers|Unban')
}.to_json
}
end
def user_ban_effects
header = tag.p s_('AdminUsers|Banning the user has the following effects:')
list = tag.ul do
concat tag.li s_('AdminUsers|User will be blocked')
end
link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") }
info = tag.p s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
header + list + info
end
def ban_feature_available?
Feature.enabled?(:ban_user_feature_flag)
end
def user_deactivation_data(user, message)
{
path: deactivate_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Deactivate user %{username}?') % { username: sanitize_name(user.name) },
messageHtml: message,
okVariant: 'warning',
okTitle: s_('AdminUsers|Deactivate')
}.to_json
}
end
def user_activation_data(user)
{
path: activate_admin_user_path(user),
method: 'put',
modal_attributes: {
title: s_('AdminUsers|Activate user %{username}?') % { username: sanitize_name(user.name) },
message: s_('AdminUsers|You can always deactivate their account again if needed.'),
okVariant: 'info',
okTitle: s_('AdminUsers|Activate')
}.to_json
}
end
def confirm_user_data(user)
message = if user.unconfirmed_email.present?
_('This user has an unconfirmed email address (%{email}). You may force a confirmation.') % { email: user.unconfirmed_email }
@ -259,22 +155,6 @@ module UsersHelper
}
end
def user_deactivation_effects
header = tag.p s_('AdminUsers|Deactivating a user has the following effects:')
list = tag.ul do
concat tag.li s_('AdminUsers|The user will be logged out')
concat tag.li s_('AdminUsers|The user will not be able to access git repositories')
concat tag.li s_('AdminUsers|The user will not be able to access the API')
concat tag.li s_('AdminUsers|The user will not receive any notifications')
concat tag.li s_('AdminUsers|The user will not be able to use slash commands')
concat tag.li s_('AdminUsers|When the user logs back in, their account will reactivate as a fully active account')
concat tag.li s_('AdminUsers|Personal projects, group and user history will be left intact')
end
header + list
end
def user_display_name(user)
return s_('UserProfile|Blocked user') if user.blocked?
@ -284,6 +164,13 @@ module UsersHelper
user.name
end
def admin_user_actions_data_attributes(user)
{
user: Admin::UserEntity.represent(user, { current_user: current_user }).to_json,
paths: admin_users_paths.to_json
}
end
private
def admin_users_paths

View File

@ -28,6 +28,8 @@ class GroupMember < Member
attr_accessor :last_owner, :last_blocked_owner
self.enumerate_columns_in_select_statements = true
def self.access_level_roles
Gitlab::Access.options_with_owner
end

View File

@ -24,7 +24,7 @@ module Groups
)
if link.save
shared_with_group.refresh_members_authorized_projects(direct_members_only: true)
shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
success(link: link)
else
error(link.errors.full_messages.to_sentence, 409)

View File

@ -16,7 +16,7 @@ module Groups
groups_to_refresh = links.map(&:shared_with_group)
groups_to_refresh.uniq.each do |group|
group.refresh_members_authorized_projects(direct_members_only: true)
group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
end
else
Gitlab::AppLogger.info(

View File

@ -13,7 +13,7 @@ module Groups
group_link.update!(group_link_params)
if requires_authorization_refresh?(group_link_params)
group_link.shared_with_group.refresh_members_authorized_projects(direct_members_only: true)
group_link.shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
end
end

View File

@ -77,7 +77,7 @@ module JiraImport
end
def project_member_ids
@project_member_ids ||= MembersFinder.new(project, current_user).execute.select(:user_id)
@project_member_ids ||= MembersFinder.new(project, current_user).execute.reselect(:user_id)
end
end
end

View File

@ -15,3 +15,5 @@
= render @identities
- else
%h4= _('This user has no identities')
= render partial: 'admin/users/modals'

View File

@ -28,3 +28,5 @@
impersonation: true,
active_tokens: @active_impersonation_tokens,
revoke_route_helper: ->(token) { revoke_admin_user_impersonation_token_path(token.user, token) }
= render partial: 'admin/users/modals'

View File

@ -1,7 +0,0 @@
.card.border-info
.card-header.gl-bg-blue-500.gl-text-white
= s_('AdminUsers|This user has requested access')
.card-body
= render partial: 'admin/users/user_approve_effects'
%br
= link_to s_('AdminUsers|Approve user'), approve_admin_user_path(user), method: :put, class: "btn gl-button btn-info", data: { confirm: s_('AdminUsers|Are you sure?'), qa_selector: 'approve_user_button' }

View File

@ -1,9 +0,0 @@
- if ban_feature_available?
.card.border-warning
.card-header.bg-warning.gl-text-white
= s_('AdminUsers|Ban user')
.card-body
= user_ban_effects
%br
%button.btn.gl-button.btn-warning.js-confirm-modal-button{ data: user_ban_data(user) }
= s_('AdminUsers|Ban user')

View File

@ -1,8 +0,0 @@
.card.border-warning
.card-header.bg-warning.text-white
= s_('AdminUsers|Block this user')
.card-body
= user_block_effects
%br
%button.btn.gl-button.btn-warning.js-confirm-modal-button{ data: user_block_data(user, s_('AdminUsers|You can always unblock their account, their data will remain intact.')) }
= s_('AdminUsers|Block user')

View File

@ -1,38 +1,38 @@
%h3.page-title
= @user.name
- if @user.blocked_pending_approval?
%span.cred
= s_('AdminUsers|(Pending approval)')
- elsif @user.banned?
%span.cred
= s_('AdminUsers|(Banned)')
- elsif @user.blocked?
%span.cred
= s_('AdminUsers|(Blocked)')
- if @user.internal?
%span.cred
= s_('AdminUsers|(Internal)')
- if @user.admin
%span.cred
= s_('AdminUsers|(Admin)')
- if @user.deactivated?
%span.cred
= s_('AdminUsers|(Deactivated)')
= render_if_exists 'admin/users/auditor_user_badge'
= render_if_exists 'admin/users/gma_user_badge'
.gl-display-flex.gl-flex-wrap.gl-justify-content-space-between.gl-align-items-center.gl-py-3.gl-mb-5.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
.gl-my-3
%h3.page-title.gl-m-0
= @user.name
- if @user.blocked_pending_approval?
%span.cred
= s_('AdminUsers|(Pending approval)')
- elsif @user.banned?
%span.cred
= s_('AdminUsers|(Banned)')
- elsif @user.blocked?
%span.cred
= s_('AdminUsers|(Blocked)')
- if @user.internal?
%span.cred
= s_('AdminUsers|(Internal)')
- if @user.admin
%span.cred
= s_('AdminUsers|(Admin)')
- if @user.deactivated?
%span.cred
= s_('AdminUsers|(Deactivated)')
= render_if_exists 'admin/users/auditor_user_badge'
= render_if_exists 'admin/users/gma_user_badge'
.float-right
= link_to edit_admin_user_path(@user), class: "btn btn-default gl-button btn-grouped" do
= sprite_icon('pencil-square', css_class: 'gl-icon gl-button-icon')
= _('Edit')
.gl-my-3.gl-display-flex.gl-flex-wrap.gl-my-n2.gl-mx-n2
.gl-p-2
#js-admin-user-actions{ data: admin_user_actions_data_attributes(@user) }
- if @user != current_user
- if impersonation_enabled? && @user.can?(:log_in)
= link_to _('Impersonate'), impersonate_admin_user_path(@user), method: :post, class: "btn btn-default gl-button btn-grouped", data: { qa_selector: 'impersonate_user_link' }
- if can_force_email_confirmation?(@user)
%button.btn.gl-button.btn-info.btn-grouped.js-confirm-modal-button{ data: confirm_user_data(@user) }
= _('Confirm user')
%hr
.gl-p-2
- if impersonation_enabled? && @user.can?(:log_in)
= link_to _('Impersonate'), impersonate_admin_user_path(@user), method: :post, class: "btn btn-default gl-button", data: { qa_selector: 'impersonate_user_link' }
- if can_force_email_confirmation?(@user)
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: confirm_user_data(@user) }
= _('Confirm user')
%ul.nav-links.nav.nav-tabs
= nav_link(path: 'users#show') do
= link_to _("Account"), admin_user_path(@user)

View File

@ -1,7 +0,0 @@
.card.border-danger
.card-header.bg-danger.gl-text-white
= s_('AdminUsers|This user has requested access')
.card-body
= render partial: 'admin/users/user_reject_effects'
%br
= link_to s_('AdminUsers|Reject request'), reject_admin_user_path(user), method: :delete, class: "btn gl-button btn-danger", data: { confirm: s_('AdminUsers|Are you sure?') }

View File

@ -1,6 +0,0 @@
%p
= s_('AdminUsers|Reactivating a user will:')
%ul
%li
= s_('AdminUsers|Restore user access to the account, including web, Git and API.')
= render_if_exists 'admin/users/user_activation_effects_on_seats'

View File

@ -1,11 +0,0 @@
%p
= s_('AdminUsers|Approved users can:')
%ul
%li
= s_('AdminUsers|Log in')
%li
= s_('AdminUsers|Access Git repositories')
%li
= s_('AdminUsers|Access the API')
%li
= s_('AdminUsers|Be added to groups and projects')

View File

@ -1,7 +1,7 @@
- if @user.note.present?
- text = @user.note
.card.border-info
.card-header.bg-info.text-white
.card
.card-header
= _('Admin Note')
.card-body
%p= text

View File

@ -1,10 +0,0 @@
%p
= s_('AdminUsers|Rejected users:')
%ul
%li
= s_('AdminUsers|Cannot sign in or access instance information')
%li
= s_('AdminUsers|Will be deleted')
%p
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path("user/profile/account/delete_account", anchor: "associated-records") }
= s_('AdminUsers|For more information, please refer to the %{link_start}user account deletion documentation.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }

View File

@ -3,3 +3,4 @@
- page_title _("SSH Keys"), @user.name, _("Users")
= render 'admin/users/head'
= render 'profiles/keys/key_table', admin: true
= render partial: 'admin/users/modals'

View File

@ -48,3 +48,5 @@
- if member.respond_to? :project
= link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn btn-sm btn-danger gl-button btn-icon gl-ml-3", title: _('Remove user from project') do
= sprite_icon('close', size: 16, css_class: 'gl-icon')
= render partial: 'admin/users/modals'

View File

@ -16,8 +16,10 @@
%strong
= link_to user_path(@user) do
= @user.username
= render 'admin/users/profile', user: @user
-# Rendered on mobile only so order of cards can be different on desktop vs mobile
.gl-md-display-none
= render 'admin/users/profile', user: @user
= render 'admin/users/user_detail_note'
.card
.card-header
= _('Account:')
@ -139,112 +141,8 @@
= render 'shared/custom_attributes', custom_attributes: @user.custom_attributes
.col-md-6
- unless @user == current_user
= render 'admin/users/user_detail_note'
- unless @user.internal?
- if @user.deactivated?
.gl-card.border-info.gl-mb-5
.gl-card-header.bg-info.text-white
= _('Reactivate this user')
.gl-card-body
= render partial: 'admin/users/user_activation_effects'
%br
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: user_activation_data(@user) }
= s_('AdminUsers|Activate user')
- elsif @user.can_be_deactivated?
.gl-card.border-warning.gl-mb-5
.gl-card-header.bg-warning.text-white
= _('Deactivate this user')
.gl-card-body
= user_deactivation_effects
%br
%button.btn.gl-button.btn-warning.js-confirm-modal-button{ data: user_deactivation_data(@user, s_('AdminUsers|You can always re-activate their account, their data will remain intact.')) }
= s_('AdminUsers|Deactivate user')
- if @user.blocked?
- if @user.blocked_pending_approval?
= render 'admin/users/approve_user', user: @user
= render 'admin/users/reject_pending_user', user: @user
- elsif @user.banned?
.gl-card.border-info.gl-mb-5
.gl-card-header.gl-bg-blue-500.gl-text-white
= _('This user is banned')
.gl-card-body
%p= _('A banned user cannot:')
%ul
%li= _('Log in')
%li= _('Access Git repositories')
- link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") }
= s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
%p
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: user_unban_data(@user) }
= s_('AdminUsers|Unban user')
- else
.gl-card.border-info.gl-mb-5
.gl-card-header.gl-bg-blue-500.gl-text-white
= _('This user is blocked')
.gl-card-body
%p= _('A blocked user cannot:')
%ul
%li= _('Log in')
%li= _('Access Git repositories')
%br
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: user_unblock_data(@user) }
= s_('AdminUsers|Unblock user')
- elsif !@user.internal?
= render 'admin/users/block_user', user: @user
= render 'admin/users/ban_user', user: @user
- if @user.access_locked?
.card.border-info.gl-mb-5
.card-header.bg-info.text-white
= _('This account has been locked')
.card-body
%p= _('This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.')
%br
= link_to _('Unlock user'), unlock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: _('Are you sure?') }
- if !@user.blocked_pending_approval?
.gl-card.border-danger.gl-mb-5
.gl-card-header.bg-danger.text-white
= s_('AdminUsers|Delete user')
.gl-card-body
- if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p= _('Deleting a user has the following effects:')
= render 'users/deletion_guidance', user: @user
%br
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(@user),
block_user_url: block_admin_user_path(@user),
username: sanitize_name(@user.name) } }
= s_('AdminUsers|Delete user')
- else
- if @user.solo_owned_groups.present?
%p
= _('This user is currently an owner in these groups:')
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
= _('You must transfer ownership or delete these groups before you can delete this user.')
- else
%p
= _("You don't have access to delete this user.")
.gl-card.border-danger
.gl-card-header.bg-danger.text-white
= s_('AdminUsers|Delete user and contributions')
.gl-card-body
- if can?(current_user, :destroy_user, @user)
%p
- link_to_ghost_user = link_to(_("system ghost user"), help_page_path("user/profile/account/delete_account"))
= _("This option deletes the user and any contributions that would usually be moved to the %{link_to_ghost_user}. As well as the user's personal projects, groups owned solely by the user, and projects in them, will also be removed. Commits to other projects are unaffected.").html_safe % { link_to_ghost_user: link_to_ghost_user }
%br
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(@user, hard_delete: true),
block_user_url: block_admin_user_path(@user),
username: @user.name } }
= s_('AdminUsers|Delete user and contributions')
- else
%p
= _("You don't have access to delete this user.")
-# Rendered on desktop only so order of cards can be different on desktop vs mobile
.col-md-6.gl-display-none.gl-md-display-block
= render 'admin/users/profile', user: @user
= render 'admin/users/user_detail_note'
= render partial: 'admin/users/modals'

View File

@ -9,6 +9,10 @@
# statement cache. If a different migration is then run and one of these columns is
# removed in the meantime, the query is invalid.
ActiveRecord::Base.class_eval do
class_attribute :enumerate_columns_in_select_statements
end
module ActiveRecord
module QueryMethods
private
@ -16,6 +20,8 @@ module ActiveRecord
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
elsif klass.enumerate_columns_in_select_statements
arel.project(*klass.column_names.map { |field| table[field] })
else
arel.project(@klass.arel_table[Arel.star])
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
class CreateVulnerabilityFlags < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
FALSE_POSITIVE_ENUM_VALUE = 0
disable_ddl_transaction!
def up
create_table_with_constraints :vulnerability_flags do |t|
t.timestamps_with_timezone null: false
t.references :vulnerability_occurrence, null: false, foreign_key: { on_delete: :cascade }
t.integer :flag_type, limit: 2, null: false, default: FALSE_POSITIVE_ENUM_VALUE
t.text :origin, null: false
t.text :description, null: false
t.text_limit :origin, 255
t.text_limit :description, 1024
end
end
def down
drop_table :vulnerability_flags
end
end

View File

@ -0,0 +1 @@
5f2acbd5ed9132ad6c11cf4be34061decde2f3c602ef319331454b424e6b4344

View File

@ -19331,6 +19331,27 @@ CREATE SEQUENCE vulnerability_findings_remediations_id_seq
ALTER SEQUENCE vulnerability_findings_remediations_id_seq OWNED BY vulnerability_findings_remediations.id;
CREATE TABLE vulnerability_flags (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
vulnerability_occurrence_id bigint NOT NULL,
flag_type smallint DEFAULT 0 NOT NULL,
origin text NOT NULL,
description text NOT NULL,
CONSTRAINT check_45e743349f CHECK ((char_length(description) <= 1024)),
CONSTRAINT check_49c1d00032 CHECK ((char_length(origin) <= 255))
);
CREATE SEQUENCE vulnerability_flags_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE vulnerability_flags_id_seq OWNED BY vulnerability_flags.id;
CREATE TABLE vulnerability_historical_statistics (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -20612,6 +20633,8 @@ ALTER TABLE ONLY vulnerability_finding_signatures ALTER COLUMN id SET DEFAULT ne
ALTER TABLE ONLY vulnerability_findings_remediations ALTER COLUMN id SET DEFAULT nextval('vulnerability_findings_remediations_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_flags ALTER COLUMN id SET DEFAULT nextval('vulnerability_flags_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_historical_statistics ALTER COLUMN id SET DEFAULT nextval('vulnerability_historical_statistics_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_identifiers ALTER COLUMN id SET DEFAULT nextval('vulnerability_identifiers_id_seq'::regclass);
@ -22342,6 +22365,9 @@ ALTER TABLE ONLY vulnerability_finding_signatures
ALTER TABLE ONLY vulnerability_findings_remediations
ADD CONSTRAINT vulnerability_findings_remediations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_flags
ADD CONSTRAINT vulnerability_flags_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_historical_statistics
ADD CONSTRAINT vulnerability_historical_statistics_pkey PRIMARY KEY (id);
@ -25251,6 +25277,8 @@ CREATE INDEX index_vulnerability_findings_remediations_on_remediation_id ON vuln
CREATE UNIQUE INDEX index_vulnerability_findings_remediations_on_unique_keys ON vulnerability_findings_remediations USING btree (vulnerability_occurrence_id, vulnerability_remediation_id);
CREATE INDEX index_vulnerability_flags_on_vulnerability_occurrence_id ON vulnerability_flags USING btree (vulnerability_occurrence_id);
CREATE INDEX index_vulnerability_historical_statistics_on_date_and_id ON vulnerability_historical_statistics USING btree (date, id);
CREATE UNIQUE INDEX index_vulnerability_identifiers_on_project_id_and_fingerprint ON vulnerability_identifiers USING btree (project_id, fingerprint);
@ -27901,6 +27929,9 @@ ALTER TABLE ONLY clusters_integration_prometheus
ALTER TABLE ONLY vulnerability_occurrence_identifiers
ADD CONSTRAINT fk_rails_e4ef6d027c FOREIGN KEY (occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_flags
ADD CONSTRAINT fk_rails_e59393b48b FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
ALTER TABLE ONLY serverless_domain_cluster
ADD CONSTRAINT fk_rails_e59e868733 FOREIGN KEY (clusters_applications_knative_id) REFERENCES clusters_applications_knative(id) ON DELETE CASCADE;

View File

@ -53,7 +53,7 @@ We need to make Docker Registry send notification events to the
registry['notifications'] = [
{
'name' => 'geo_event',
'url' => 'https://example.com/api/v4/container_registry_event/events',
'url' => 'https://<example.com>/api/v4/container_registry_event/events',
'timeout' => '500ms',
'threshold' => 5,
'backoff' => '1s',
@ -65,7 +65,8 @@ We need to make Docker Registry send notification events to the
```
NOTE:
Replace `<replace_with_a_secret_token>` with a case sensitive alphanumeric string
Replace `<example.com>` with the `external_url` defined in your primary site's `/etc/gitlab/gitlab.rb` file, and
replace `<replace_with_a_secret_token>` with a case sensitive alphanumeric string
that starts with a letter. You can generate one with `< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32 | sed "s/^[0-9]*//"; echo`
NOTE:

View File

@ -60,7 +60,7 @@ GET /projects
| `simple` | boolean | **{dotted-circle}** No | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. |
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
| `starred` | boolean | **{dotted-circle}** No | Limit by projects starred by the current user. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. Only available to Reporter or higher level role members. |
| `topic` | string | **{dotted-circle}** No | Comma-separated topic names. Limit results to projects that match all of given topics. See `topics` attribute. |
| `visibility` | string | **{dotted-circle}** No | Limit by visibility `public`, `internal`, or `private`. |
| `wiki_checksum_failed` **(PREMIUM)** | boolean | **{dotted-circle}** No | Limit projects where the wiki checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2). |
@ -379,7 +379,7 @@ GET /users/:user_id/projects
| `simple` | boolean | **{dotted-circle}** No | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. |
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
| `starred` | boolean | **{dotted-circle}** No | Limit by projects starred by the current user. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. Only available to Reporter or higher level role members. |
| `user_id` | string | **{check-circle}** Yes | The ID or username of the user. |
| `visibility` | string | **{dotted-circle}** No | Limit by visibility `public`, `internal`, or `private`. |
| `with_custom_attributes` | boolean | **{dotted-circle}** No | Include [custom attributes](custom_attributes.md) in response. _(admins only)_ |
@ -612,7 +612,7 @@ GET /users/:user_id/starred_projects
| `simple` | boolean | **{dotted-circle}** No | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned.. |
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
| `starred` | boolean | **{dotted-circle}** No | Limit by projects starred by the current user. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. Only available to Reporter or higher level role members. |
| `user_id` | string | **{check-circle}** Yes | The ID or username of the user. |
| `visibility` | string | **{dotted-circle}** No | Limit by visibility `public`, `internal`, or `private`. |
| `with_custom_attributes` | boolean | **{dotted-circle}** No | Include [custom attributes](custom_attributes.md) in response. _(admins only)_ |
@ -831,7 +831,7 @@ GET /projects/:id
|--------------------------|----------------|------------------------|-------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
| `license` | boolean | **{dotted-circle}** No | Include project license data. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. Only available to Reporter or higher level role members. |
| `with_custom_attributes` | boolean | **{dotted-circle}** No | Include [custom attributes](custom_attributes.md) in response. _(admins only)_ |
```json
@ -1425,7 +1425,7 @@ GET /projects/:id/forks
| `simple` | boolean | **{dotted-circle}** No | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. |
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
| `starred` | boolean | **{dotted-circle}** No | Limit by projects starred by the current user. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. Only available to Reporter or higher level role members. |
| `visibility` | string | **{dotted-circle}** No | Limit by visibility `public`, `internal`, or `private`. |
| `with_custom_attributes` | boolean | **{dotted-circle}** No | Include [custom attributes](custom_attributes.md) in response. _(admins only)_ |
| `with_issues_enabled` | boolean | **{dotted-circle}** No | Limit by enabled issues feature. |

View File

@ -384,7 +384,7 @@ Before taking the decision to merge:
- Set the milestone.
- Consider warnings and errors from danger bot, code quality, and other reports.
Unless a strong case can be made for the violation, these should be resolved
before merging. A comment must to be posted if the MR is merged with any failed job.
before merging. A comment must be posted if the MR is merged with any failed job.
- If the MR contains both Quality and non-Quality-related changes, the MR should be merged by the relevant maintainer for user-facing changes (backend, frontend, or database) after the Quality related changes are approved by a Software Engineer in Test.
If a merge request is fundamentally ready, but needs only trivial fixes (such as

View File

@ -62,7 +62,7 @@ Avoid forcing links to open in a new window as this reduces the control the user
However, it might be a good idea to use a blank target when replacing the current page with
the link makes the user lose content or progress.
Use `rel="noopener noreferrer"` whenever your links open in a new window, i.e. `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/).
Use `rel="noopener noreferrer"` whenever your links open in a new window, that is, `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/).
When using `gl-link`, using `target="_blank"` is sufficient as it automatically adds `rel="noopener noreferrer"` to the link.

View File

@ -51,7 +51,7 @@ We recommend a "utility-first" approach.
1. Start with utility classes.
1. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it.
This encourages an organic growth of component classes and prevents the creation of one-off non-reusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (e.g. `.button`, `.alert`, `.card`) rather than domain-centered (e.g. `.security-report-widget`, `.commit-header-icon`).
This encourages an organic growth of component classes and prevents the creation of one-off non-reusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (for example, `.button`, `.alert`, `.card`) rather than domain-centered (for example, `.security-report-widget`, `.commit-header-icon`).
Inspiration:

View File

@ -221,7 +221,7 @@ you should fully roll out the feature by enabling the flag **globally** by runni
```
This changes the feature flag state to be **enabled** always, which overrides the
existing gates (e.g. `--group=gitlab-org`) in the above processes.
existing gates (for example, `--group=gitlab-org`) in the above processes.
Note, that if an actor based feature gate is present, switching the
`default_enabled` attribute of the YAML definition from `false` to `true`

View File

@ -61,7 +61,7 @@ When the feature implementation is delivered among multiple merge requests:
One might be tempted to think that feature flags will delay the release of a
feature by at least one month (= one release). This is not the case. A feature
flag does not have to stick around for a specific amount of time
(e.g. at least one release), instead they should stick around until the feature
(for example, at least one release), instead they should stick around until the feature
is deemed stable. Stable means it works on GitLab.com without causing any
problems, such as outages.

View File

@ -17,7 +17,7 @@ end
Here you will need to add a foreign key on column `posts.user_id`. This ensures
that data consistency is enforced on database level. Foreign keys also mean that
the database can very quickly remove associated data (e.g. when removing a
the database can very quickly remove associated data (for example, when removing a
user), instead of Rails having to do this.
## Adding Foreign Keys In Migrations

View File

@ -56,7 +56,7 @@ Geo uses [streaming replication](#streaming-replication) to replicate
the database from the **primary** to the **secondary** nodes. This
replication gives the **secondary** nodes access to all the data saved
in the database. So users can log in on the **secondary** and read all
the issues, merge requests, etc. on the **secondary** node.
the issues, merge requests, and so on, on the **secondary** node.
### Repository replication
@ -127,7 +127,7 @@ periodically to sync all uploads that aren't synced to the Geo
Files are copied via HTTP(s) and initiated via the
`/api/v4/geo/transfers/:type/:id` endpoint,
e.g. `/api/v4/geo/transfers/lfs/123`.
for example, `/api/v4/geo/transfers/lfs/123`.
## Authentication
@ -219,7 +219,7 @@ bundle exec rake geo:db:migrate
Geo uses [Finders](https://gitlab.com/gitlab-org/gitlab/-/tree/master/app/finders),
which are classes take care of the heavy lifting of looking up
projects/attachments/etc. in the tracking database and main database.
projects/attachments/ and so on, in the tracking database and main database.
## Redis
@ -228,7 +228,7 @@ node. It is used for caching, storing sessions, and other persistent
data.
Redis data replication between **primary** and **secondary** node is
not used, so sessions etc. aren't shared between nodes.
not used, so sessions and so on, aren't shared between nodes.
## Object Storage

View File

@ -163,7 +163,7 @@ repository and a pool.
### Pool existence
If GitLab thinks a pool repository exists (i.e. it exists according to
If GitLab thinks a pool repository exists (that is, it exists according to
SQL), but it does not on the Gitaly server, then it is created on
the fly by Gitaly.

View File

@ -145,7 +145,7 @@ long we're still performing work.
GitHub has a rate limit of 5,000 API calls per hour. The number of requests
necessary to import a project is largely dominated by the number of unique users
involved in a project (e.g. issue authors). Other data such as issue pages
involved in a project (for example, issue authors). Other data such as issue pages
and comments typically only requires a few dozen requests to import. This is
because we need the Email address of users in order to map them to GitLab users.

View File

@ -14,7 +14,7 @@ projects using the [Go language](https://golang.org).
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org/), but we're
also using Go for projects where it makes sense. Go is a very powerful
language, with many advantages, and is best suited for projects with a lot of
IO (disk/network access), HTTP requests, parallel processing, etc. Since we
IO (disk/network access), HTTP requests, parallel processing, and so on. Since we
have both Ruby on Rails and Go at GitLab, we should evaluate carefully which of
the two is best for the job.
@ -390,7 +390,7 @@ consistent across Workhorse, Gitaly, and, in future, other Go servers. For
example, in the case of `gitlab.com/gitlab-org/labkit/tracing` we can switch
from using `Opentracing` directly to using `Zipkin` or Gokit's own tracing wrapper
without changes to the application code, while still keeping the same
consistent configuration mechanism (i.e. the `GITLAB_TRACING` environment
consistent configuration mechanism (that is, the `GITLAB_TRACING` environment
variable).
### Context
@ -437,7 +437,7 @@ and the version being used for [CNG](https://gitlab.com/gitlab-org/build/cng/blo
### Updating Go version
We should always use a [supported version](https://golang.org/doc/devel/release#policy)
of Go, i.e., one of the three most recent minor releases, and should always use
of Go, that is, one of the three most recent minor releases, and should always use
the most recent patch-level for that version, as it may contain security fixes.
Changing the version affects every project being compiled, so it's important to

View File

@ -29,11 +29,14 @@ See the [merge request process](https://about.gitlab.com/handbook/ceo/chief-of-s
on the JiHu Support handbook.
This page is the single source of truth for JiHu-related processes.
## Act as EE when `jh/` does not exist
## Act as EE when `jh/` does not exist or when `EE_ONLY=1`
- In the case of EE repository, `jh/` does not exist so it should just act like EE (or CE when the license is absent)
- In the case of JH repository, `jh/` does exist but `EE_ONLY` environment variable can be set to force it run under EE mode.
- In the case of JH repository, `jh/` does exist but `FOSS_ONLY` environment variable can be set to force it run under CE mode.
## Act as FOSS when `FOSS_ONLY=1`
- In the case of JH repository, `jh/` does exist but `FOSS_ONLY` environment variable can be set to force it run under FOSS (CE) mode.
## CI pipelines in a JH context

View File

@ -58,12 +58,12 @@ Structured logging solves these problems. Consider the example from an API reque
In a single line, we've included all the information that a user needs
to understand what happened: the timestamp, HTTP method and path, user
ID, etc.
ID, and so on.
### How to use JSON logging
Suppose you want to log the events that happen in a project
importer. You want to log issues created, merge requests, etc. as the
importer. You want to log issues created, merge requests, and so on, as the
importer progresses. Here's what to do:
1. Look at [the list of GitLab Logs](../administration/logs.md) to see
@ -174,7 +174,7 @@ Resources:
Similar to timezones, choosing the right time unit to log can impose avoidable overhead. So, whenever
challenged to choose between seconds, milliseconds or any other unit, lean towards _seconds_ as float
(with microseconds precision, i.e. `Gitlab::InstrumentationHelper::DURATION_PRECISION`).
(with microseconds precision, that is, `Gitlab::InstrumentationHelper::DURATION_PRECISION`).
In order to make it easier to track timings in the logs, make sure the log key has `_s` as
suffix and `duration` within its name (for example, `view_duration_s`).

View File

@ -179,9 +179,9 @@ As a counterpart of the `without_sticky_writes` utility,
replicas regardless of the current primary stickiness.
This utility is reserved for cases where queries can tolerate replication lag.
Internally, our database load balancer classifies the queries based on their main statement (`select`, `update`, `delete`, etc.). When in doubt, it redirects the queries to the primary database. Hence, there are some common cases the load balancer sends the queries to the primary unnecessarily:
Internally, our database load balancer classifies the queries based on their main statement (`select`, `update`, `delete`, and so on). When in doubt, it redirects the queries to the primary database. Hence, there are some common cases the load balancer sends the queries to the primary unnecessarily:
- Custom queries (via `exec_query`, `execute_statement`, `execute`, etc.)
- Custom queries (via `exec_query`, `execute_statement`, `execute`, and so on)
- Read-only transactions
- In-flight connection configuration set
- Sidekiq background jobs
@ -197,7 +197,7 @@ costly, time-consuming query to the replicas.
Read about [complex queries on the relation object](iterating_tables_in_batches.md#complex-queries-on-the-relation-object) for considerations on how to use CTEs. We have found in some situations that CTEs can become problematic in use (similar to the n+1 problem above). In particular, hierarchical recursive CTE queries such as the CTE in [AuthorizedProjectsWorker](https://gitlab.com/gitlab-org/gitlab/-/issues/325688) are very difficult to optimize and don't scale. We should avoid them when implementing new features that require any kind of hierarchical structure.
CTEs have been effectively used as an optimization fence in many simpler cases,
CTEs have been effectively used as an optimization fence in many simpler cases,
such as this [example](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/43242#note_61416277).
Beginning in PostgreSQL 12, CTEs are inlined then [optimized by default](https://paquier.xyz/postgresql-2/postgres-12-with-materialize/).
Keeping the old behavior requires marking CTEs with the keyword `MATERIALIZED`.
@ -567,7 +567,7 @@ to work with you to possibly discover a better solution.
The usage of local storage is a desired solution to use,
especially since we work on deploying applications to Kubernetes clusters.
When you would like to use `Dir.mktmpdir`? In a case when you want for example
to extract/create archives, perform extensive manipulation of existing data, etc.
to extract/create archives, perform extensive manipulation of existing data, and so on.
```ruby
Dir.mktmpdir('designs') do |path|

View File

@ -299,6 +299,8 @@ end
**Creating a new table when we have two foreign keys:**
Only one foreign key should be created per migration. This is because [the addition of a foreign key constraint requires a `SHARE ROW EXCLUSIVE` lock on the referenced table](https://www.postgresql.org/docs/12/sql-createtable.html#:~:text=The%20addition%20of%20a%20foreign%20key%20constraint%20requires%20a%20SHARE%20ROW%20EXCLUSIVE%20lock%20on%20the%20referenced%20table), and locking multiple tables in the same transaction should be avoided.
For this, we need three migrations:
1. Creating the table without foreign keys (with the indices).
@ -605,7 +607,7 @@ perform existence checks internally.
When adding a foreign-key constraint to either an existing or a new column also
remember to add an index on the column.
This is **required** for all foreign-keys, e.g., to support efficient cascading
This is **required** for all foreign-keys, for example, to support efficient cascading
deleting: when a lot of rows in a table get deleted, the referenced records need
to be deleted too. The database has to look for corresponding records in the
referenced table. Without an index, this results in a sequential scan on the

View File

@ -49,9 +49,9 @@ instance variables in the final giant object, and that's where the problem is.
## Solutions
We should split the giant object into multiple objects, and they communicate
with each other with the API, i.e. public methods. In short, composition over
with each other with the API, that is, public methods. In short, composition over
inheritance. This way, each smaller objects would have their own respective
limited states, i.e. instance variables. If one instance variable goes wrong,
limited states, that is, instance variables. If one instance variable goes wrong,
we would be very clear that it's from that single small object, because
no one else could be touching it.

View File

@ -304,7 +304,7 @@ variable `CI_NODE_TOTAL` being an integer failed. This was caused because after
1. As a result, the [new code](https://gitlab.com/gitlab-org/gitlab/-/blob/42b82a9a3ac5a96f9152aad6cbc583c42b9fb082/app/models/concerns/ci/contextable.rb#L104)
was not run on the API server. The runner's request failed because the
older API server tried return the `CI_NODE_TOTAL` CI/CD variable, but
instead of sending an integer value (e.g. 9), it sent a serialized
instead of sending an integer value (for example, 9), it sent a serialized
`Hash` value (`{:number=>9, :total=>9}`).
If you look at the [deployment pipeline](https://ops.gitlab.net/gitlab-com/gl-infra/deployer/-/pipelines/202212),

View File

@ -12,7 +12,7 @@ when you are coding.
## Files are owned by root by default
All the files in the Rails tree (`app/`, `config/` etc.) are owned by `root` in
All the files in the Rails tree (`app/`, `config/`, and so on) are owned by `root` in
Omnibus installations. This makes the installation simpler and it provides
extra security. The Omnibus reconfigure script contains commands that give
write access to the `git` user only where needed.

View File

@ -191,7 +191,7 @@ against the project or group before continuing.
The current database model allows you to store a name and a version for each package.
Every time you upload a new package, you can either create a new record of `Package`
or add files to existing record. `PackageFile` should be able to store all file-related
information like the file `name`, `side`, `sha1`, etc.
information like the file `name`, `side`, `sha1`, and so on.
If there is specific data necessary to be stored for only one package system support,
consider creating a separate metadata model. See `packages_maven_metadata` table

View File

@ -120,7 +120,7 @@ allowing you to profile which code is running on CPU in detail.
It's important to note that profiling an application *alters its performance*.
Different profiling strategies have different overheads. Stackprof is a sampling
profiler. It samples stack traces from running threads at a configurable
frequency (e.g. 100hz, that is 100 stacks per second). This type of profiling
frequency (for example, 100hz, that is 100 stacks per second). This type of profiling
has quite a low (albeit non-zero) overhead and is generally considered to be
safe for production.

View File

@ -51,7 +51,7 @@ depending on the changes made in the MR:
We use the [`rules:`](../ci/yaml/index.md#rules) and [`needs:`](../ci/yaml/index.md#needs) keywords extensively
to determine the jobs that need to be run in a pipeline. Note that an MR that includes multiple types of changes would
have a pipelines that include jobs from multiple types (e.g. a combination of docs-only and code-only pipelines).
have a pipelines that include jobs from multiple types (for example, a combination of docs-only and code-only pipelines).
#### Documentation only MR pipeline
@ -557,7 +557,7 @@ request, be sure to start the `dont-interrupt-me` job before pushing.
- `.yarn-cache`
- `.assets-compile-cache` (the key includes `${NODE_ENV}` so it's actually two different caches).
1. These cache definitions are composed of [multiple atomic caches](../ci/caching/index.md#use-multiple-caches).
1. Only the following jobs, running in 2-hourly scheduled pipelines, are pushing (i.e. updating) to the caches:
1. Only the following jobs, running in 2-hourly scheduled pipelines, are pushing (that is, updating) to the caches:
- `update-setup-test-env-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-gitaly-binaries-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-static-analysis-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
@ -653,7 +653,7 @@ The current stages are:
- `fixtures`: This stage includes jobs that prepare fixtures needed by frontend tests.
- `test`: This stage includes most of the tests, DB/migration jobs, and static analysis jobs.
- `post-test`: This stage includes jobs that build reports or gather data from
the `test` stage's jobs (e.g. coverage, Knapsack metadata etc.).
the `test` stage's jobs (for example, coverage, Knapsack metadata, and so on).
- `review-prepare`: This stage includes a job that build the CNG images that are
later used by the (Helm) Review App deployment (see
[Review Apps](testing_guide/review_apps.md) for details).
@ -663,9 +663,9 @@ that is deployed in stage `review`.
- `qa`: This stage includes jobs that perform QA tasks against the Review App
that is deployed in stage `review`.
- `post-qa`: This stage includes jobs that build reports or gather data from
the `qa` stage's jobs (e.g. Review App performance report).
the `qa` stage's jobs (for example, Review App performance report).
- `pages`: This stage includes a job that deploys the various reports as
GitLab Pages (e.g. [`coverage-ruby`](https://gitlab-org.gitlab.io/gitlab/coverage-ruby/),
GitLab Pages (for example, [`coverage-ruby`](https://gitlab-org.gitlab.io/gitlab/coverage-ruby/),
[`coverage-javascript`](https://gitlab-org.gitlab.io/gitlab/coverage-javascript/),
and `webpack-report` (found at `https://gitlab-org.gitlab.io/gitlab/webpack-report/`, but there is
[an issue with the deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/233458)).
@ -721,7 +721,7 @@ that are scoped to a single [configuration keyword](../ci/yaml/index.md#job-keyw
| Job definitions | Description |
|------------------|-------------|
| `.default-retry` | Allows a job to [retry](../ci/yaml/index.md#retry) upon `unknown_failure`, `api_failure`, `runner_system_failure`, `job_execution_timeout`, or `stuck_or_timeout_failure`. |
| `.default-before_script` | Allows a job to use a default `before_script` definition suitable for Ruby/Rails tasks that may need a database running (e.g. tests). |
| `.default-before_script` | Allows a job to use a default `before_script` definition suitable for Ruby/Rails tasks that may need a database running (for example, tests). |
| `.setup-test-env-cache` | Allows a job to use a default `cache` definition suitable for setting up test environment for subsequent Ruby/Rails tasks. |
| `.rails-cache` | Allows a job to use a default `cache` definition suitable for Ruby/Rails tasks. |
| `.static-analysis-cache` | Allows a job to use a default `cache` definition suitable for static analysis tasks. |
@ -757,8 +757,8 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/index.md#ancho
| `if:` conditions | Description | Notes |
|------------------|-------------|-------|
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success|manual`), or **not** create a job for forks (by using `when: never`). |
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
| `if-not-ee` | Matches if the project isn't EE (that is, project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
| `if-not-foss` | Matches if the project isn't FOSS (that is, project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
| `if-default-refs` | Matches if the pipeline is for `master`, `main`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
| `if-master-refs` | Matches if the current branch is `master` or `main`. | |
| `if-master-push` | Matches if the current branch is `master` or `main` and pipeline source is `push`. | |
@ -797,11 +797,11 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/index.md#ancho
| `ci-qa-patterns` | Only create job for CI configuration-related changes related to the `qa` stage. |
| `yaml-lint-patterns` | Only create job for YAML-related changes. |
| `docs-patterns` | Only create job for docs-related changes. |
| `frontend-dependency-patterns` | Only create job when frontend dependencies are updated (i.e. `package.json`, and `yarn.lock`). changes. |
| `frontend-dependency-patterns` | Only create job when frontend dependencies are updated (that is, `package.json`, and `yarn.lock`). changes. |
| `frontend-patterns` | Only create job for frontend-related changes. |
| `backend-patterns` | Only create job for backend-related changes. |
| `db-patterns` | Only create job for DB-related changes. |
| `backstage-patterns` | Only create job for backstage-related changes (i.e. Danger, fixtures, RuboCop, specs). |
| `backstage-patterns` | Only create job for backstage-related changes (that is, Danger, fixtures, RuboCop, specs). |
| `code-patterns` | Only create job for code-related changes. |
| `qa-patterns` | Only create job for QA-related changes. |
| `code-backstage-patterns` | Combination of `code-patterns` and `backstage-patterns`. |

View File

@ -106,7 +106,7 @@ Each line represents a rule that was evaluated. There are a few things to note:
1. The `-` or `+` symbol indicates whether the rule block was evaluated to be
`false` or `true`, respectively.
1. The number inside the brackets indicates the score.
1. The last part of the line (e.g. `@john : Issue/1`) shows the username
1. The last part of the line (for example, `@john : Issue/1`) shows the username
and subject for that rule.
Here you can see that the first four rules were evaluated `false` for
@ -150,7 +150,7 @@ then the result of the condition is cached globally only based on the subject -
**DANGER**: If you use a `:scope` option when the condition actually uses data from
both user and subject (including a simple anonymous check!) your result is cached at too global of a scope and results in cache bugs.
Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - that is, tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
```ruby
def users_that_can_read_project(users, project)

View File

@ -146,7 +146,7 @@ filter rows using the `IS NULL` condition.
To summarize: using separate tables allows us to use foreign keys effectively,
create indexes only where necessary, conserve space, query data more
efficiently, and scale these tables more easily (e.g. by storing them on
efficiently, and scale these tables more easily (for example, by storing them on
separate disks). A nice side effect of this is that code can also become easier,
as a single model isn't responsible for handling different kinds of
data.

View File

@ -129,7 +129,7 @@ end
## Repeat last command
You can repeat the last command by just hitting the <kbd>Enter</kbd>
key (e.g., with `step` or`next`), if you place the following snippet
key (for example, with `step` or`next`), if you place the following snippet
in your `~/.pryrc`:
```ruby

View File

@ -48,7 +48,7 @@ end
Use a [request spec](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/spec/requests) when writing a N+1 test on the controller level.
Controller specs should not be used to write N+1 tests as the controller is only initialized once per example.
This could lead to false successes where subsequent "requests" could have queries reduced (e.g. because of memoization).
This could lead to false successes where subsequent "requests" could have queries reduced (for example, because of memoization).
## Finding the source of the query

View File

@ -19,4 +19,4 @@ Ruby files in this folder are loaded in alphabetical order just like the default
Some examples where you would need to do this are:
1. Modifying Rails' `config.autoload_paths`
1. Changing configuration that Zeitwerk uses, e.g. inflections
1. Changing configuration that Zeitwerk uses, for example, inflections

View File

@ -258,7 +258,7 @@ self.reactive_cache_hard_limit = 5.megabytes
- This is the type of work performed by the `calculate_reactive_cache` method. Based on this attribute,
it's able to pick the right worker to process the caching job. Make sure to
set it as `:external_dependency` if the work performs any external request
(e.g. Kubernetes, Sentry); otherwise set it to `:no_dependency`.
(for example, Kubernetes, Sentry); otherwise set it to `:no_dependency`.
#### `self.reactive_cache_worker_finder`

View File

@ -14,7 +14,7 @@ Pinning tests help you ensure that you don't unintentionally change the output o
### Example steps
1. Identify all the possible inputs to the refactor subject (e.g. anything that's injected into the template or used in a conditional).
1. Identify all the possible inputs to the refactor subject (for example, anything that's injected into the template or used in a conditional).
1. For each possible input, identify the significant possible values.
1. Create a test to save a full detailed snapshot for each helpful combination values per input. This should guarantee that we have "pinned down" the current behavior. The snapshot could be literally a screenshot, a dump of HTML, or even an ordered list of debugging statements.
1. Run all the pinning tests against the code, before you start refactoring (Oracle)

View File

@ -12,7 +12,7 @@ Sometimes the business asks to change the name of a feature. Broadly speaking, t
- Pros: does not increase code complexity.
- Cons: more work to execute, and higher risk of immediate bugs.
- Façade, rename as little as possible; only the user-facing content like interfaces,
documentation, error messages, etc.
documentation, error messages, and so on.
- Pros: less work to execute.
- Cons: increases code complexity, creating higher risk of future bugs.

View File

@ -215,7 +215,7 @@ provided by Active Record are not included, except for the following methods:
### Active Record
The API provided by Active Record itself, such as the `where` method, `save`,
`delete_all`, etc.
`delete_all`, and so on.
### Worker

View File

@ -23,7 +23,7 @@ users. We discuss each component below.
### PostgreSQL
The PostgreSQL database holds all metadata for projects, issues, merge
requests, users, etc. The schema is managed by the Rails application
requests, users, and so on. The schema is managed by the Rails application
[db/structure.sql](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/structure.sql).
GitLab Web/API servers and Sidekiq nodes talk directly to the database by using a
@ -91,7 +91,7 @@ ownership. It shares a lot of challenges with traditional, data-oriented
sharding, however. For instance, joining data has to happen in the
application itself rather than on the query layer (although additional
layers like GraphQL might mitigate that) and it requires true
parallelism to run efficiently (i.e. a scatter-gather model to collect,
parallelism to run efficiently (that is, a scatter-gather model to collect,
then zip up data records), which is a challenge in itself in Ruby based
systems.

View File

@ -49,7 +49,7 @@ Each time you implement a new feature/endpoint, whether it is at UI, API or Grap
- Do not forget **abuse cases**: write specs that **make sure certain things can't happen**
- A lot of specs are making sure things do happen and coverage percentage doesn't take into account permissions as same piece of code is used.
- Make assertions that certain actors cannot perform actions
- Naming convention to ease auditability: to be defined, e.g. a subfolder containing those specific permission tests or a `#permissions` block
- Naming convention to ease auditability: to be defined, for example, a subfolder containing those specific permission tests or a `#permissions` block
Be careful to **also test [visibility levels](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/doc/development/permissions.md#feature-specific-permissions)** and not only project access rights.
@ -59,13 +59,13 @@ Some example of well implemented access controls and tests:
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3170/diffs?diff_id=17494)
**NB:** any input from development team is welcome, e.g. about Rubocop rules.
**NB:** any input from development team is welcome, for example, about Rubocop rules.
## Regular Expressions guidelines
### Anchors / Multi line
Unlike other programming languages (e.g. Perl or Python) Regular Expressions are matching multi-line by default in Ruby. Consider the following example in Python:
Unlike other programming languages (for example, Perl or Python) Regular Expressions are matching multi-line by default in Ruby. Consider the following example in Python:
```python
import re

View File

@ -35,7 +35,7 @@ turn there's no way to query the data at all.
## Waste Of Space
Storing serialized data such as JSON or YAML will end up wasting a lot of space.
This is because these formats often include additional characters (e.g. double
This is because these formats often include additional characters (for example, double
quotes or newlines) besides the data that you are storing.
## Difficult To Manage
@ -69,9 +69,9 @@ can easily take hours or even days to complete.
## Relational Databases Are Not Document Stores
When storing data as JSON or YAML you're essentially using your database as if
it were a document store (e.g. MongoDB), except you're not using any of the
it were a document store (for example, MongoDB), except you're not using any of the
powerful features provided by a typical RDBMS _nor_ are you using any of the
features provided by a typical document store (e.g. the ability to index fields
features provided by a typical document store (for example, the ability to index fields
of documents with variable fields). In other words, it's a waste.
## Consistent Fields

View File

@ -274,13 +274,31 @@ If you add a member to a group by using the [share a group with another group](.
CI pipeline minutes are the execution time for your [pipelines](../../ci/pipelines/index.md)
on GitLab shared runners. Each [GitLab SaaS tier](https://about.gitlab.com/pricing/)
includes a monthly quota of CI pipeline minutes for private and public projects:
includes a monthly quota of CI pipeline minutes for private and public projects in
the namespace:
| Plan | Private projects | Public projects |
|----------|------------------|-----------------|
| Free | 400 | 50,000 |
| Premium | 10,000 | 1,250,000 |
| Ultimate | 50,000 | 6,250,000 |
| Plan | CI pipeline minutes |
|----------|---------------------|
| Free | 400 |
| Premium | 10,000 |
| Ultimate | 50,000 |
The consumption rate for CI pipeline minutes is based on the visibility of the projects:
- Private projects in the namespace consume pipeline minutes at a rate of 1 CI pipeline minute
per 1 minute of execution time on GitLab shared runners.
- Public projects in:
- Namespaces [created on or after 2021-07-17](https://gitlab.com/gitlab-org/gitlab/-/issues/332708)
consume pipeline minutes at a slower rate, 1 CI pipeline minute per 125 minutes
of execution time on GitLab shared runners. The per-minute rate for public projects
is 0.008 CI pipeline minutes per 1 minute of execution time on GitLab shared runners.
- Namespaces created before 2021-07-17 do not consume CI pipeline minutes.
| Plan | CI pipeline minutes | Maximum **private** project execution time (all namespaces) | Maximum **public** project execution time (namespaces created 2021-07-17 and later) |
|----------|---------------------|-------------------------------------------------------------|-------------------------------------------------------------------------------------|
| Free | 400 | 400 minutes | 50,000 minutes |
| Premium | 10,000 | 10,000 minutes | 1,250,000 minutes |
| Ultimate | 50,000 | 50,000 minutes | 6,250,000 minutes |
Quotas apply to:

View File

@ -47,7 +47,8 @@ To approve or reject a user sign up:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select the **Pending approval** tab.
1. In the user's row, select settings (**{settings}**).
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Approve** or **Reject**.
Approving a user:

View File

@ -23,8 +23,9 @@ or directly from the Admin Area. To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select a user.
1. Under the **Account** tab, select **Block user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Block**.
A blocked user:
@ -47,8 +48,9 @@ A blocked user can be unblocked from the Admin Area. To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select on the **Blocked** tab.
1. Select a user.
1. Under the **Account** tab, select **Unblock user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Unblock**.
Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user).
@ -85,8 +87,9 @@ A user can be deactivated from the Admin Area. To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select a user.
1. Under the **Account** tab, select **Deactivate user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Deactivate**.
Please note that for the deactivation option to be visible to an admin, the user:
@ -126,8 +129,9 @@ To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select the **Deactivated** tab.
1. Select a user.
1. Under the **Account** tab, select **Activate user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Activate**.
Users can also be activated using the [GitLab API](../../api/users.md#activate-user).
@ -157,8 +161,9 @@ Users can be banned using the Admin Area. To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select a user.
1. Under the **Account** tab, select **Ban user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Ban user**.
NOTE:
This feature is a work in progress. Currently, banning a user
@ -172,8 +177,9 @@ A banned user can be unbanned using the Admin Area. To do this:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select the **Banned** tab.
1. Select a user.
1. Under the **Account** tab, select **Unban user**.
1. (Optional) Select a user.
1. Select the **{settings}** **User administration** dropdown.
1. Select **Unban user**.
NOTE:
Unbanning a user changes the user's state to active and consumes a

View File

@ -74,6 +74,10 @@ best to [back up](../../../raketasks/backup_restore.md)
the existing instance and restore it on the new instance. For example, this is useful when migrating
a self-managed instance from an old server to a new server.
The backups produced don't depend on the operating system running GitLab. You can therefore use
the restore method to switch between different operating system distributions or versions, as long
as the same GitLab version [is available for installation](https://docs.gitlab.com/omnibus/package-information/deprecated_os.md).
To instead merge two self-managed GitLab instances together, use the instructions in
[Migrate from self-managed GitLab to GitLab.com](#migrate-from-self-managed-gitlab-to-gitlabcom).
This method is useful when both self-managed instances have existing data that must be preserved.

View File

@ -69,7 +69,7 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. |
| `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. |
| `/estimate <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. Learn more about [time tracking](time_tracking.md). |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants`. |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. |
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). |
| `/label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
| `/lock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Lock the discussions. |

View File

@ -171,3 +171,26 @@ current default branch, instead of displaying the "not found" page.
- [Discussion of default branch renaming](https://lore.kernel.org/git/pull.656.v4.git.1593009996.gitgitgadget@gmail.com/)
on the Git mailing list
- [March 2021 blog post: The new Git default branch name](https://about.gitlab.com/blog/2021/03/10/new-git-default-branch-name/)
## Troubleshooting
### Unable to change default branch: resets to current branch
We are tracking this problem in [issue 20474](https://gitlab.com/gitlab-org/gitlab/-/issues/20474).
This issue often occurs when a branch named `HEAD` is present in the repository.
To fix the problem:
1. In your local repository, create a new, temporary branch and push it:
```shell
git checkout -b tmp_default && git push -u origin tmp_default
```
1. In GitLab, proceed to [change the default branch](#change-the-default-branch-name-for-a-project) to that temporary branch.
1. From your local repository, delete the `HEAD` branch:
```shell
git push -d origin HEAD
```
1. In GitLab, [change the default branch](#change-the-default-branch-name-for-a-project) to the one you intend to use.

View File

@ -39,7 +39,7 @@ module Gitlab
end
def <=>(other)
sort_keys <=> other.sort_keys
sort_keys.compact <=> other.sort_keys.compact
end
protected

View File

@ -1405,18 +1405,12 @@ msgstr ""
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
msgstr ""
msgid "A banned user cannot:"
msgstr ""
msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
msgstr ""
msgid "A basic template for developing Linux programs using Kotlin Native"
msgstr ""
msgid "A blocked user cannot:"
msgstr ""
msgid "A complete DevOps platform"
msgstr ""
@ -1702,9 +1696,6 @@ msgstr ""
msgid "Acceptable for use in this project"
msgstr ""
msgid "Access Git repositories"
msgstr ""
msgid "Access Git repositories or the API."
msgstr ""
@ -2518,9 +2509,6 @@ msgstr ""
msgid "AdminUsers|Activate"
msgstr ""
msgid "AdminUsers|Activate user"
msgstr ""
msgid "AdminUsers|Activate user %{username}?"
msgstr ""
@ -2542,24 +2530,15 @@ msgstr ""
msgid "AdminUsers|Approve"
msgstr ""
msgid "AdminUsers|Approve user"
msgstr ""
msgid "AdminUsers|Approve user %{username}?"
msgstr ""
msgid "AdminUsers|Approved users can:"
msgstr ""
msgid "AdminUsers|Are you sure?"
msgstr ""
msgid "AdminUsers|Automatically marked as default internal user"
msgstr ""
msgid "AdminUsers|Ban"
msgstr ""
msgid "AdminUsers|Ban user"
msgstr ""
@ -2569,18 +2548,12 @@ msgstr ""
msgid "AdminUsers|Banned"
msgstr ""
msgid "AdminUsers|Banning the user has the following effects:"
msgstr ""
msgid "AdminUsers|Be added to groups and projects"
msgstr ""
msgid "AdminUsers|Block"
msgstr ""
msgid "AdminUsers|Block this user"
msgstr ""
msgid "AdminUsers|Block user"
msgstr ""
@ -2620,9 +2593,6 @@ msgstr ""
msgid "AdminUsers|Deactivate"
msgstr ""
msgid "AdminUsers|Deactivate user"
msgstr ""
msgid "AdminUsers|Deactivate user %{username}?"
msgstr ""
@ -2710,9 +2680,6 @@ msgstr ""
msgid "AdminUsers|Reject"
msgstr ""
msgid "AdminUsers|Reject request"
msgstr ""
msgid "AdminUsers|Reject user %{username}?"
msgstr ""
@ -2749,21 +2716,12 @@ msgstr ""
msgid "AdminUsers|The user will not receive any notifications"
msgstr ""
msgid "AdminUsers|This user has requested access"
msgstr ""
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr ""
msgid "AdminUsers|To confirm, type %{username}"
msgstr ""
msgid "AdminUsers|Unban"
msgstr ""
msgid "AdminUsers|Unban %{username}?"
msgstr ""
msgid "AdminUsers|Unban user"
msgstr ""
@ -2773,19 +2731,16 @@ msgstr ""
msgid "AdminUsers|Unblock"
msgstr ""
msgid "AdminUsers|Unblock user"
msgstr ""
msgid "AdminUsers|Unblock user %{username}?"
msgstr ""
msgid "AdminUsers|Unlock user %{username}?"
msgstr ""
msgid "AdminUsers|User is validated and can use free CI minutes on shared runners."
msgid "AdminUsers|User administration"
msgstr ""
msgid "AdminUsers|User will be blocked"
msgid "AdminUsers|User is validated and can use free CI minutes on shared runners."
msgstr ""
msgid "AdminUsers|User will not be able to access git repositories"
@ -2830,9 +2785,6 @@ msgstr ""
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered."
msgstr ""
msgid "AdminUsers|You ban their account in the future if necessary."
msgstr ""
msgid "AdminUsers|You can always block their account again if needed."
msgstr ""
@ -10449,9 +10401,6 @@ msgstr ""
msgid "Deactivate dormant users after 90 days of inactivity. Users can return to active status by signing in to their account. While inactive, a user is not counted as an active user in the instance."
msgstr ""
msgid "Deactivate this user"
msgstr ""
msgid "Dear Administrator,"
msgstr ""
@ -10701,9 +10650,6 @@ msgstr ""
msgid "Deleting a project places it into a read-only state until %{date}, at which point the project will be permanently deleted. Are you ABSOLUTELY sure?"
msgstr ""
msgid "Deleting a user has the following effects:"
msgstr ""
msgid "Deleting the project will delete its repository and all related resources, including issues and merge requests."
msgstr ""
@ -19868,9 +19814,6 @@ msgstr ""
msgid "Locks the discussion."
msgstr ""
msgid "Log in"
msgstr ""
msgid "Login with smartcard"
msgstr ""
@ -26921,9 +26864,6 @@ msgstr ""
msgid "Re-verification interval"
msgstr ""
msgid "Reactivate this user"
msgstr ""
msgid "Read documentation"
msgstr ""
@ -33360,9 +33300,6 @@ msgstr ""
msgid "This URL is already used for another link; duplicate URLs are not allowed"
msgstr ""
msgid "This account has been locked"
msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
@ -33708,9 +33645,6 @@ msgstr ""
msgid "This only applies to repository indexing operations."
msgstr ""
msgid "This option deletes the user and any contributions that would usually be moved to the %{link_to_ghost_user}. As well as the user's personal projects, groups owned solely by the user, and projects in them, will also be removed. Commits to other projects are unaffected."
msgstr ""
msgid "This option is only available on GitLab.com"
msgstr ""
@ -33813,9 +33747,6 @@ msgstr ""
msgid "This user has an unconfirmed email address. You may force a confirmation."
msgstr ""
msgid "This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account."
msgstr ""
msgid "This user has no active %{type}."
msgstr ""
@ -33831,15 +33762,6 @@ msgstr ""
msgid "This user has the %{access} role in the %{name} project."
msgstr ""
msgid "This user is banned"
msgstr ""
msgid "This user is blocked"
msgstr ""
msgid "This user is currently an owner in these groups:"
msgstr ""
msgid "This user is the author of this %{noteable}."
msgstr ""
@ -35100,9 +35022,6 @@ msgstr ""
msgid "Unlock this %{issuableDisplayName}? %{strongStart}Everyone%{strongEnd} will be able to comment."
msgstr ""
msgid "Unlock user"
msgstr ""
msgid "Unlocked"
msgstr ""
@ -37623,9 +37542,6 @@ msgstr ""
msgid "You do not have permissions to run the import."
msgstr ""
msgid "You don't have access to delete this user."
msgstr ""
msgid "You don't have any U2F devices registered yet."
msgstr ""
@ -37773,9 +37689,6 @@ msgstr ""
msgid "You must solve the CAPTCHA in order to submit"
msgstr ""
msgid "You must transfer ownership or delete these groups before you can delete this user."
msgstr ""
msgid "You must upload a file with the same file name when dropping onto an existing design."
msgstr ""
@ -39748,9 +39661,6 @@ msgstr ""
msgid "suggestPipeline|Were adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "system ghost user"
msgstr ""
msgid "tag name"
msgstr ""

View File

@ -14,8 +14,13 @@ module QA
element :user_id_content
end
view 'app/views/admin/users/_approve_user.html.haml' do
view 'app/assets/javascripts/admin/users/components/actions/approve.vue' do
element :approve_user_button
element :approve_user_confirm_button
end
view 'app/assets/javascripts/admin/users/components/user_actions.vue' do
element :user_actions_dropdown_toggle
end
view 'app/helpers/users_helper.rb' do
@ -23,6 +28,10 @@ module QA
element :confirm_user_confirm_button
end
def open_user_actions_dropdown(user)
click_element(:user_actions_dropdown_toggle, username: user.username)
end
def click_impersonate_user
click_element(:impersonate_user_link)
end
@ -36,10 +45,10 @@ module QA
click_element :confirm_user_confirm_button
end
def approve_user
accept_confirm do
click_element :approve_user_button
end
def approve_user(user)
open_user_actions_dropdown(user)
click_element :approve_user_button
click_element :approve_user_confirm_button
end
end
end

View File

@ -143,7 +143,7 @@ module QA
Page::Admin::Overview::Users::Show.perform do |show|
user.id = show.user_id.to_i
show.approve_user
show.approve_user(user)
end
expect(page).to have_text('Successfully approved')

View File

@ -88,7 +88,7 @@ RSpec.describe Groups::GroupLinksController do
end
end
it 'updates project permissions' do
it 'updates project permissions', :sidekiq_inline do
expect { subject }.to change { group_member.can?(:read_project, project) }.from(false).to(true)
end
@ -207,7 +207,7 @@ RSpec.describe Groups::GroupLinksController do
end
end
it 'updates project permissions' do
it 'updates project permissions', :sidekiq_inline do
expect { subject }.to change { group_member.can?(:create_release, project) }.from(true).to(false)
end
end
@ -244,7 +244,7 @@ RSpec.describe Groups::GroupLinksController do
expect { subject }.to change(GroupGroupLink, :count).by(-1)
end
it 'updates project permissions' do
it 'updates project permissions', :sidekiq_inline do
expect { subject }.to change { group_member.can?(:create_release, project) }.from(true).to(false)
end
end

View File

@ -4,6 +4,8 @@ require 'spec_helper'
# Test an operation that triggers background jobs requiring administrative rights
RSpec.describe 'Admin mode for workers', :request_store do
include Spec::Support::Helpers::Features::AdminUsersHelpers
let(:user) { create(:user) }
let(:user_to_delete) { create(:user) }
@ -37,7 +39,8 @@ RSpec.describe 'Admin mode for workers', :request_store do
it 'can delete user', :js do
visit admin_user_path(user_to_delete)
click_button 'Delete user'
click_action_in_user_dropdown(user_to_delete.id, 'Delete user')
page.within '.modal-dialog' do
find("input[name='username']").send_keys(user_to_delete.name)

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Admin::Users::User' do
include Spec::Support::Helpers::Features::AdminUsersHelpers
let_it_be(:user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
let_it_be(:current_user) { create(:admin) }
@ -12,15 +14,18 @@ RSpec.describe 'Admin::Users::User' do
end
describe 'GET /admin/users/:id' do
it 'has user info', :aggregate_failures do
it 'has user info', :js, :aggregate_failures do
visit admin_user_path(user)
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
expect(page).to have_content("ID: #{user.id}")
expect(page).to have_content("Namespace ID: #{user.namespace_id}")
expect(page).to have_button('Deactivate user')
expect(page).to have_button('Block user')
click_user_dropdown_toggle(user.id)
expect(page).to have_button('Block')
expect(page).to have_button('Deactivate')
expect(page).to have_button('Delete user')
expect(page).to have_button('Delete user and contributions')
end
@ -29,9 +34,7 @@ RSpec.describe 'Admin::Users::User' do
it 'shows confirmation and allows blocking and unblocking', :js do
visit admin_user_path(user)
find('button', text: 'Block user').click
wait_for_requests
click_action_in_user_dropdown(user.id, 'Block')
expect(page).to have_content('Block user')
expect(page).to have_content('You can always unblock their account, their data will remain intact.')
@ -41,21 +44,18 @@ RSpec.describe 'Admin::Users::User' do
wait_for_requests
expect(page).to have_content('Successfully blocked')
expect(page).to have_content('This user is blocked')
find('button', text: 'Unblock user').click
wait_for_requests
click_action_in_user_dropdown(user.id, 'Unblock')
expect(page).to have_content('Unblock user')
expect(page).to have_content('You can always block their account again if needed.')
find('.modal-footer button', text: 'Unblock').click
wait_for_requests
expect(page).to have_content('Successfully unblocked')
expect(page).to have_content('Block this user')
click_user_dropdown_toggle(user.id)
expect(page).to have_content('Block')
end
end
@ -63,9 +63,7 @@ RSpec.describe 'Admin::Users::User' do
it 'shows confirmation and allows deactivating/re-activating', :js do
visit admin_user_path(user)
find('button', text: 'Deactivate user').click
wait_for_requests
click_action_in_user_dropdown(user.id, 'Deactivate')
expect(page).to have_content('Deactivate user')
expect(page).to have_content('You can always re-activate their account, their data will remain intact.')
@ -75,11 +73,8 @@ RSpec.describe 'Admin::Users::User' do
wait_for_requests
expect(page).to have_content('Successfully deactivated')
expect(page).to have_content('Reactivate this user')
find('button', text: 'Activate user').click
wait_for_requests
click_action_in_user_dropdown(user.id, 'Activate')
expect(page).to have_content('Activate user')
expect(page).to have_content('You can always deactivate their account again if needed.')
@ -89,7 +84,9 @@ RSpec.describe 'Admin::Users::User' do
wait_for_requests
expect(page).to have_content('Successfully activated')
expect(page).to have_content('Deactivate this user')
click_user_dropdown_toggle(user.id)
expect(page).to have_content('Deactivate')
end
end
@ -367,8 +364,11 @@ RSpec.describe 'Admin::Users::User' do
expect(page).to have_content(user.name)
expect(page).to have_content('Pending approval')
expect(page).to have_link('Approve user')
expect(page).to have_link('Reject request')
click_user_dropdown_toggle(user.id)
expect(page).to have_button('Approve')
expect(page).to have_button('Reject')
end
end
end

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Admin::Users' do
include Spec::Support::Helpers::Features::AdminUsersHelpers
let_it_be(:user, reload: true) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
let_it_be(:current_user) { create(:admin) }
@ -572,12 +574,6 @@ RSpec.describe 'Admin::Users' do
end
end
def click_user_dropdown_toggle(user_id)
page.within("[data-testid='user-actions-#{user_id}']") do
find("[data-testid='dropdown-toggle']").click
end
end
def first_row
page.all('[role="row"]')[1]
end
@ -592,14 +588,4 @@ RSpec.describe 'Admin::Users' do
click_link option
end
end
def click_action_in_user_dropdown(user_id, action)
click_user_dropdown_toggle(user_id)
within find("[data-testid='user-actions-#{user_id}']") do
find('li button', text: action).click
end
wait_for_requests
end
end

View File

@ -1,4 +1,5 @@
import { GlDropdownDivider } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Actions from '~/admin/users/components/actions';
import AdminUserActions from '~/admin/users/components/user_actions.vue';
@ -20,7 +21,7 @@ describe('AdminUserActions component', () => {
findUserActions(id).find('[data-testid="dropdown-toggle"]');
const findDropdownDivider = () => wrapper.findComponent(GlDropdownDivider);
const initComponent = ({ actions = [] } = {}) => {
const initComponent = ({ actions = [], showButtonLabels } = {}) => {
wrapper = shallowMountExtended(AdminUserActions, {
propsData: {
user: {
@ -28,6 +29,10 @@ describe('AdminUserActions component', () => {
actions,
},
paths,
showButtonLabels,
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
@ -144,4 +149,42 @@ describe('AdminUserActions component', () => {
});
});
});
describe('when `showButtonLabels` prop is `false`', () => {
beforeEach(() => {
initComponent({ actions: [EDIT, ...CONFIRMATION_ACTIONS] });
});
it('does not render "Edit" button label', () => {
const tooltip = getBinding(findEditButton().element, 'gl-tooltip');
expect(findEditButton().text()).toBe('');
expect(findEditButton().attributes('aria-label')).toBe(I18N_USER_ACTIONS.edit);
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe(I18N_USER_ACTIONS.edit);
});
it('does not render "User administration" dropdown button label', () => {
expect(findActionsDropdown().props('text')).toBe(I18N_USER_ACTIONS.userAdministration);
expect(findActionsDropdown().props('textSrOnly')).toBe(true);
});
});
describe('when `showButtonLabels` prop is `true`', () => {
beforeEach(() => {
initComponent({ actions: [EDIT, ...CONFIRMATION_ACTIONS], showButtonLabels: true });
});
it('renders "Edit" button label', () => {
const tooltip = getBinding(findEditButton().element, 'gl-tooltip');
expect(findEditButton().text()).toBe(I18N_USER_ACTIONS.edit);
expect(tooltip).not.toBeDefined();
});
it('renders "User administration" dropdown button label', () => {
expect(findActionsDropdown().props('text')).toBe(I18N_USER_ACTIONS.userAdministration);
expect(findActionsDropdown().props('textSrOnly')).toBe(false);
});
});
});

View File

@ -1,7 +1,8 @@
import { createWrapper } from '@vue/test-utils';
import { initAdminUsersApp } from '~/admin/users';
import { initAdminUsersApp, initAdminUserActions } from '~/admin/users';
import AdminUsersApp from '~/admin/users/components/app.vue';
import { users, paths } from './mock_data';
import UserActions from '~/admin/users/components/user_actions.vue';
import { users, user, paths } from './mock_data';
describe('initAdminUsersApp', () => {
let wrapper;
@ -14,15 +15,12 @@ describe('initAdminUsersApp', () => {
el.setAttribute('data-users', JSON.stringify(users));
el.setAttribute('data-paths', JSON.stringify(paths));
document.body.appendChild(el);
wrapper = createWrapper(initAdminUsersApp(el));
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
el.remove();
el = null;
});
@ -33,3 +31,31 @@ describe('initAdminUsersApp', () => {
});
});
});
describe('initAdminUserActions', () => {
let wrapper;
let el;
const findUserActions = () => wrapper.find(UserActions);
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-user', JSON.stringify(user));
el.setAttribute('data-paths', JSON.stringify(paths));
wrapper = createWrapper(initAdminUserActions(el));
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
el = null;
});
it('parses and passes props', () => {
expect(findUserActions().props()).toMatchObject({
user,
paths,
});
});
});

View File

@ -18,6 +18,8 @@ export const users = [
},
];
export const user = users[0];
export const paths = {
edit: '/admin/users/id/edit',
approve: '/admin/users/id/approve',

View File

@ -396,4 +396,22 @@ RSpec.describe UsersHelper do
end
end
end
describe '#admin_user_actions_data_attributes' do
subject(:data) { helper.admin_user_actions_data_attributes(user) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(Admin::UserEntity).to receive(:represent).and_call_original
end
it 'user matches the serialized json' do
expect(data[:user]).to be_valid_json
expect(Admin::UserEntity).to have_received(:represent).with(user, hash_including({ current_user: user }))
end
it 'paths matches the schema' do
expect(data[:paths]).to match_schema('entities/admin_users_data_attributes_paths')
end
end
end

View File

@ -110,6 +110,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Scanner do
{ external_id: 'gemnasium-python', name: 'foo', vendor: 'bar' } | { external_id: 'bandit', name: 'foo', vendor: 'bar' } | 1
{ external_id: 'bandit', name: 'foo', vendor: 'bar' } | { external_id: 'semgrep', name: 'foo', vendor: 'bar' } | -1
{ external_id: 'semgrep', name: 'foo', vendor: 'bar' } | { external_id: 'unknown', name: 'foo', vendor: 'bar' } | -1
{ external_id: 'gemnasium', name: 'foo', vendor: 'bar' } | { external_id: 'gemnasium', name: 'foo', vendor: nil } | 1
end
with_them do

View File

@ -24,7 +24,7 @@ RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
expect { subject.execute(link) }.to change { shared_group.shared_with_group_links.count }.from(1).to(0)
end
it 'revokes project authorization' do
it 'revokes project authorization', :sidekiq_inline do
group.add_developer(user)
expect { subject.execute(link) }.to(
@ -47,8 +47,8 @@ RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
it 'updates project authorization once per group' do
expect(GroupGroupLink).to receive(:delete).and_call_original
expect(group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true).once
expect(another_group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true).once
expect(group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true, blocking: false).once
expect(another_group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true, blocking: false).once
subject.execute(links)
end

View File

@ -36,7 +36,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
expect(link.expires_at).to eq(expiry_date)
end
it 'updates project permissions' do
it 'updates project permissions', :sidekiq_inline do
expect { subject }.to change { group_member_user.can?(:create_release, project) }.from(true).to(false)
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Spec
module Support
module Helpers
module Features
module AdminUsersHelpers
def click_user_dropdown_toggle(user_id)
page.within("[data-testid='user-actions-#{user_id}']") do
find("[data-testid='dropdown-toggle']").click
end
end
def click_action_in_user_dropdown(user_id, action)
click_user_dropdown_toggle(user_id)
within find("[data-testid='user-actions-#{user_id}']") do
find('li button', exact_text: action).click
end
end
end
end
end
end
end

View File

@ -51,7 +51,7 @@ RSpec.describe RemoveExpiredGroupLinksWorker do
subject.perform
end
it 'removes project authorization' do
it 'removes project authorization', :sidekiq_inline do
shared_group = group_group_link.shared_group
shared_with_group = group_group_link.shared_with_group
project = create(:project, group: shared_group)