Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4a72126942
commit
3f0db3db2a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { initAdminUserActions, initDeleteUserModals } from '~/admin/users';
|
||||
import initConfirmModal from '~/confirm_modal';
|
||||
|
||||
initAdminUserActions();
|
||||
initDeleteUserModals();
|
||||
initConfirmModal();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { initExpiresAtField } from '~/access_tokens';
|
||||
import { initAdminUserActions, initDeleteUserModals } from '~/admin/users';
|
||||
import initConfirmModal from '~/confirm_modal';
|
||||
|
||||
initAdminUserActions();
|
||||
initDeleteUserModals();
|
||||
initExpiresAtField();
|
||||
initConfirmModal();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@
|
|||
= render @identities
|
||||
- else
|
||||
%h4= _('This user has no identities')
|
||||
|
||||
= render partial: 'admin/users/modals'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
@ -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')
|
||||
|
|
@ -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')
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?') }
|
||||
|
|
@ -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'
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
5f2acbd5ed9132ad6c11cf4be34061decde2f3c602ef319331454b424e6b4344
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def <=>(other)
|
||||
sort_keys <=> other.sort_keys
|
||||
sort_keys.compact <=> other.sort_keys.compact
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
|||
|
|
@ -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|We’re 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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue