diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-19 21:08:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-19 21:08:23 +0300 |
commit | 3f0db3db2ad99a74c3969bf2e930814004ccf1ec (patch) | |
tree | c8b4123b3b4b422b14211430b85556e64eb68a26 | |
parent | 4a721269429a178957e8ce7c6d0a75d3307c9830 (diff) |
Add latest changes from gitlab-org/gitlab@master
91 files changed, 577 insertions, 618 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index a34c1b354c2..c7da1c5bebc 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -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 diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 1e60e6684cb..3aa8ab1591a 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -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 diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue index b782526e6be..c076e0bedf0 100644 --- a/app/assets/javascripts/admin/users/components/user_actions.vue +++ b/app/assets/javascripts/admin/users/components/user_actions.vue @@ -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> diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index 33ee7e1cb0d..4636c8705a5 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -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'), diff --git a/app/assets/javascripts/admin/users/index.js b/app/assets/javascripts/admin/users/index.js index 05f8469e61a..852b253d25a 100644 --- a/app/assets/javascripts/admin/users/index.js +++ b/app/assets/javascripts/admin/users/index.js @@ -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); diff --git a/app/assets/javascripts/pages/admin/identities/index.js b/app/assets/javascripts/pages/admin/identities/index.js index 868c8e33077..a9f5f00cb9b 100644 --- a/app/assets/javascripts/pages/admin/identities/index.js +++ b/app/assets/javascripts/pages/admin/identities/index.js @@ -1,3 +1,6 @@ +import { initAdminUserActions, initDeleteUserModals } from '~/admin/users'; import initConfirmModal from '~/confirm_modal'; +initAdminUserActions(); +initDeleteUserModals(); initConfirmModal(); diff --git a/app/assets/javascripts/pages/admin/impersonation_tokens/index.js b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js index 3f9e4e9d591..8fbc8dc17bc 100644 --- a/app/assets/javascripts/pages/admin/impersonation_tokens/index.js +++ b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js @@ -1,5 +1,8 @@ import { initExpiresAtField } from '~/access_tokens'; +import { initAdminUserActions, initDeleteUserModals } from '~/admin/users'; import initConfirmModal from '~/confirm_modal'; +initAdminUserActions(); +initDeleteUserModals(); initExpiresAtField(); initConfirmModal(); diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js index 99bf842ef2d..41e99a3baf5 100644 --- a/app/assets/javascripts/pages/admin/users/index.js +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -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(); diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js index f1d9ced807b..b36c9c0d049 100644 --- a/app/assets/javascripts/pipelines/components/parsing_utils.js +++ b/app/assets/javascripts/pipelines/components/parsing_utils.js @@ -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 }; }; diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb index 1ff2ad01b63..ea101cf1dcd 100644 --- a/app/finders/members_finder.rb +++ b/app/finders/members_finder.rb @@ -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) diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 2af932f8e8c..93a0166f43e 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -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 diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index ab044b80133..cf5906a4cbf 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -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 diff --git a/app/services/groups/group_links/create_service.rb b/app/services/groups/group_links/create_service.rb index 5f81e5972b0..8c3ba0a63f2 100644 --- a/app/services/groups/group_links/create_service.rb +++ b/app/services/groups/group_links/create_service.rb @@ -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) diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb index 05504a80f46..0e7fd7e0817 100644 --- a/app/services/groups/group_links/destroy_service.rb +++ b/app/services/groups/group_links/destroy_service.rb @@ -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( diff --git a/app/services/groups/group_links/update_service.rb b/app/services/groups/group_links/update_service.rb index 3703d535482..a1411de36d6 100644 --- a/app/services/groups/group_links/update_service.rb +++ b/app/services/groups/group_links/update_service.rb @@ -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 diff --git a/app/services/jira_import/users_mapper_service.rb b/app/services/jira_import/users_mapper_service.rb index 760f06a1cfb..13e0dd5120e 100644 --- a/app/services/jira_import/users_mapper_service.rb +++ b/app/services/jira_import/users_mapper_service.rb @@ -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 diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml index a6d562dad31..d85ab476693 100644 --- a/app/views/admin/identities/index.html.haml +++ b/app/views/admin/identities/index.html.haml @@ -15,3 +15,5 @@ = render @identities - else %h4= _('This user has no identities') + += render partial: 'admin/users/modals' diff --git a/app/views/admin/impersonation_tokens/index.html.haml b/app/views/admin/impersonation_tokens/index.html.haml index 465025ba684..1609687fc8d 100644 --- a/app/views/admin/impersonation_tokens/index.html.haml +++ b/app/views/admin/impersonation_tokens/index.html.haml @@ -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' diff --git a/app/views/admin/users/_approve_user.html.haml b/app/views/admin/users/_approve_user.html.haml deleted file mode 100644 index f61c9fa4b80..00000000000 --- a/app/views/admin/users/_approve_user.html.haml +++ /dev/null @@ -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' } diff --git a/app/views/admin/users/_ban_user.html.haml b/app/views/admin/users/_ban_user.html.haml deleted file mode 100644 index 229c88adb7f..00000000000 --- a/app/views/admin/users/_ban_user.html.haml +++ /dev/null @@ -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') diff --git a/app/views/admin/users/_block_user.html.haml b/app/views/admin/users/_block_user.html.haml deleted file mode 100644 index 29029986345..00000000000 --- a/app/views/admin/users/_block_user.html.haml +++ /dev/null @@ -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') diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index 9ef2d7b2f22..b7b712e078d 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -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) diff --git a/app/views/admin/users/_reject_pending_user.html.haml b/app/views/admin/users/_reject_pending_user.html.haml deleted file mode 100644 index 17108427330..00000000000 --- a/app/views/admin/users/_reject_pending_user.html.haml +++ /dev/null @@ -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?') } diff --git a/app/views/admin/users/_user_activation_effects.html.haml b/app/views/admin/users/_user_activation_effects.html.haml deleted file mode 100644 index 244836dac11..00000000000 --- a/app/views/admin/users/_user_activation_effects.html.haml +++ /dev/null @@ -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' diff --git a/app/views/admin/users/_user_approve_effects.html.haml b/app/views/admin/users/_user_approve_effects.html.haml deleted file mode 100644 index 54e51bf3467..00000000000 --- a/app/views/admin/users/_user_approve_effects.html.haml +++ /dev/null @@ -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') diff --git a/app/views/admin/users/_user_detail_note.html.haml b/app/views/admin/users/_user_detail_note.html.haml index 4f2a682c5ca..cc4827327c9 100644 --- a/app/views/admin/users/_user_detail_note.html.haml +++ b/app/views/admin/users/_user_detail_note.html.haml @@ -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 diff --git a/app/views/admin/users/_user_reject_effects.html.haml b/app/views/admin/users/_user_reject_effects.html.haml deleted file mode 100644 index 17b6862b0cc..00000000000 --- a/app/views/admin/users/_user_reject_effects.html.haml +++ /dev/null @@ -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 } diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml index 5f9d11af7c1..28024ae084f 100644 --- a/app/views/admin/users/keys.html.haml +++ b/app/views/admin/users/keys.html.haml @@ -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' diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 3ff726e1945..8c56e888dcc 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -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' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 5477ece7439..ad8d9d1f04f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -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' diff --git a/config/initializers/active_record_build_select.rb b/config/initializers/active_record_build_select.rb index ab5a872cac6..48f1b1ee407 100644 --- a/config/initializers/active_record_build_select.rb +++ b/config/initializers/active_record_build_select.rb @@ -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 diff --git a/db/migrate/20210707171554_create_vulnerability_flags.rb b/db/migrate/20210707171554_create_vulnerability_flags.rb new file mode 100644 index 00000000000..bf33963b08f --- /dev/null +++ b/db/migrate/20210707171554_create_vulnerability_flags.rb @@ -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 diff --git a/db/schema_migrations/20210707171554 b/db/schema_migrations/20210707171554 new file mode 100644 index 00000000000..ef6f174f734 --- /dev/null +++ b/db/schema_migrations/20210707171554 @@ -0,0 +1 @@ +5f2acbd5ed9132ad6c11cf4be34061decde2f3c602ef319331454b424e6b4344
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 45b1d0bc835..da2d4c50068 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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; diff --git a/doc/administration/geo/replication/docker_registry.md b/doc/administration/geo/replication/docker_registry.md index 83007767215..5cc4f66017b 100644 --- a/doc/administration/geo/replication/docker_registry.md +++ b/doc/administration/geo/replication/docker_registry.md @@ -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: diff --git a/doc/api/projects.md b/doc/api/projects.md index ebf1c70c887..dc9e59f0cad 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -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. | diff --git a/doc/development/code_review.md b/doc/development/code_review.md index a24a4ecc266..929e75e7774 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -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 diff --git a/doc/development/fe_guide/style/html.md b/doc/development/fe_guide/style/html.md index 18f72a9655c..72492d56ee4 100644 --- a/doc/development/fe_guide/style/html.md +++ b/doc/development/fe_guide/style/html.md @@ -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. diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index e05ef7c9f5d..4a9446f2949 100644 --- a/doc/development/fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md @@ -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: diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md index 4ad3136408b..c69a698149e 100644 --- a/doc/development/feature_flags/controls.md +++ b/doc/development/feature_flags/controls.md @@ -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` diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md index ca830ca54b9..1962d5262ce 100644 --- a/doc/development/feature_flags/index.md +++ b/doc/development/feature_flags/index.md @@ -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. diff --git a/doc/development/foreign_keys.md b/doc/development/foreign_keys.md index e234cf9a177..a9edbc68a2e 100644 --- a/doc/development/foreign_keys.md +++ b/doc/development/foreign_keys.md @@ -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 diff --git a/doc/development/geo.md b/doc/development/geo.md index 0ec394dc764..38245e5f4e5 100644 --- a/doc/development/geo.md +++ b/doc/development/geo.md @@ -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 diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md index 1607f3e7a12..3ac24b19fc2 100644 --- a/doc/development/git_object_deduplication.md +++ b/doc/development/git_object_deduplication.md @@ -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. diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md index 3e70585499d..a733d6881fa 100644 --- a/doc/development/github_importer.md +++ b/doc/development/github_importer.md @@ -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. diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md index 72435e8f503..224d8a0a0f5 100644 --- a/doc/development/go_guide/index.md +++ b/doc/development/go_guide/index.md @@ -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 diff --git a/doc/development/jh_features_review.md b/doc/development/jh_features_review.md index b139a380344..cb0f8ddbd13 100644 --- a/doc/development/jh_features_review.md +++ b/doc/development/jh_features_review.md @@ -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 diff --git a/doc/development/logging.md b/doc/development/logging.md index 45f5b672365..cb1070b49cc 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -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`). diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md index 90976f04214..d87b7bcb5af 100644 --- a/doc/development/merge_request_performance_guidelines.md +++ b/doc/development/merge_request_performance_guidelines.md @@ -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| diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 62e797276c1..f76b053c2bd 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -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 diff --git a/doc/development/module_with_instance_variables.md b/doc/development/module_with_instance_variables.md index f298b603429..0f910f20534 100644 --- a/doc/development/module_with_instance_variables.md +++ b/doc/development/module_with_instance_variables.md @@ -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. diff --git a/doc/development/multi_version_compatibility.md b/doc/development/multi_version_compatibility.md index a12c01c734a..3314b5e7ddc 100644 --- a/doc/development/multi_version_compatibility.md +++ b/doc/development/multi_version_compatibility.md @@ -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), diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md index 5b97221bd29..dc83b0ea257 100644 --- a/doc/development/omnibus.md +++ b/doc/development/omnibus.md @@ -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. diff --git a/doc/development/packages.md b/doc/development/packages.md index fa0cdbef92e..94882cefc30 100644 --- a/doc/development/packages.md +++ b/doc/development/packages.md @@ -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 diff --git a/doc/development/performance.md b/doc/development/performance.md index 84b3a8f1092..e59f7fb154b 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -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. diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index b58e644d639..0fe48fe8b9e 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -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`. | diff --git a/doc/development/policies.md b/doc/development/policies.md index 315878e19d9..8ad3d3f42ba 100644 --- a/doc/development/policies.md +++ b/doc/development/policies.md @@ -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) diff --git a/doc/development/polymorphic_associations.md b/doc/development/polymorphic_associations.md index f341255a7e1..bbeaab40a90 100644 --- a/doc/development/polymorphic_associations.md +++ b/doc/development/polymorphic_associations.md @@ -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. diff --git a/doc/development/pry_debugging.md b/doc/development/pry_debugging.md index 402029164a7..5481da348e8 100644 --- a/doc/development/pry_debugging.md +++ b/doc/development/pry_debugging.md @@ -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 diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md index 46866f67f68..8759bd09538 100644 --- a/doc/development/query_recorder.md +++ b/doc/development/query_recorder.md @@ -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 diff --git a/doc/development/rails_initializers.md b/doc/development/rails_initializers.md index 89902c81cd5..ee73dac2b72 100644 --- a/doc/development/rails_initializers.md +++ b/doc/development/rails_initializers.md @@ -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 diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md index 4d01078868b..3c0a1419604 100644 --- a/doc/development/reactive_caching.md +++ b/doc/development/reactive_caching.md @@ -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` diff --git a/doc/development/refactoring_guide/index.md b/doc/development/refactoring_guide/index.md index a25000589c0..a6ed83258f3 100644 --- a/doc/development/refactoring_guide/index.md +++ b/doc/development/refactoring_guide/index.md @@ -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) diff --git a/doc/development/renaming_features.md b/doc/development/renaming_features.md index f7fc1c11e37..bd25fa1377e 100644 --- a/doc/development/renaming_features.md +++ b/doc/development/renaming_features.md @@ -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. diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md index 1e200e1f520..ded6b074324 100644 --- a/doc/development/reusing_abstractions.md +++ b/doc/development/reusing_abstractions.md @@ -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 diff --git a/doc/development/scalability.md b/doc/development/scalability.md index 675f0968ec5..824c98b4b03 100644 --- a/doc/development/scalability.md +++ b/doc/development/scalability.md @@ -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. diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md index 74f65034383..fc60c1d7d7f 100644 --- a/doc/development/secure_coding_guidelines.md +++ b/doc/development/secure_coding_guidelines.md @@ -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 diff --git a/doc/development/serializing_data.md b/doc/development/serializing_data.md index f5924a44753..48e756d015b 100644 --- a/doc/development/serializing_data.md +++ b/doc/development/serializing_data.md @@ -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 diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md index 32f587d900e..fad415807bd 100644 --- a/doc/subscriptions/gitlab_com/index.md +++ b/doc/subscriptions/gitlab_com/index.md @@ -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: - -| Plan | Private projects | Public projects | -|----------|------------------|-----------------| -| Free | 400 | 50,000 | -| Premium | 10,000 | 1,250,000 | -| Ultimate | 50,000 | 6,250,000 | +includes a monthly quota of CI pipeline minutes for private and public projects in +the namespace: + +| 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: diff --git a/doc/user/admin_area/approving_users.md b/doc/user/admin_area/approving_users.md index 3d9722035d5..2852f73ffc8 100644 --- a/doc/user/admin_area/approving_users.md +++ b/doc/user/admin_area/approving_users.md @@ -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: diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md index e2b4e97d824..3889dd93d59 100644 --- a/doc/user/admin_area/moderate_users.md +++ b/doc/user/admin_area/moderate_users.md @@ -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 diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index 2dcd4a23870..dcc41c6c85e 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -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. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 23f72d823c4..d8d464ce6d8 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -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. | diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md index 0f4c831216a..2f1171a7d4f 100644 --- a/doc/user/project/repository/branches/default.md +++ b/doc/user/project/repository/branches/default.md @@ -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. diff --git a/lib/gitlab/ci/reports/security/scanner.rb b/lib/gitlab/ci/reports/security/scanner.rb index de5c354b5ab..c1de03cea44 100644 --- a/lib/gitlab/ci/reports/security/scanner.rb +++ b/lib/gitlab/ci/reports/security/scanner.rb @@ -39,7 +39,7 @@ module Gitlab end def <=>(other) - sort_keys <=> other.sort_keys + sort_keys.compact <=> other.sort_keys.compact end protected diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b46bb1d50ae..5a8c95a0ebf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/qa/qa/page/admin/overview/users/show.rb b/qa/qa/page/admin/overview/users/show.rb index 64c31b0b534..be73f3d80bf 100644 --- a/qa/qa/page/admin/overview/users/show.rb +++ b/qa/qa/page/admin/overview/users/show.rb @@ -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 diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 23a21d70cc1..696bbc2a7b7 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -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') diff --git a/spec/controllers/groups/group_links_controller_spec.rb b/spec/controllers/groups/group_links_controller_spec.rb index 94d3c1ffa0f..fafe9715946 100644 --- a/spec/controllers/groups/group_links_controller_spec.rb +++ b/spec/controllers/groups/group_links_controller_spec.rb @@ -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 diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb index fbbcf19063b..0caa883fb5b 100644 --- a/spec/features/admin/admin_mode/workers_spec.rb +++ b/spec/features/admin/admin_mode/workers_spec.rb @@ -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) diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 9766bd48a8f..e6eb76b13eb 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -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 diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index 187fa6fc2a4..119b01ff552 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -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 diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js index 50471551268..43313424553 100644 --- a/spec/frontend/admin/users/components/user_actions_spec.js +++ b/spec/frontend/admin/users/components/user_actions_spec.js @@ -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); + }); + }); }); diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js index 20b60bd8640..06dbadd6d3d 100644 --- a/spec/frontend/admin/users/index_spec.js +++ b/spec/frontend/admin/users/index_spec.js @@ -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, + }); + }); +}); diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js index e7cb4088bf1..ded3e6f7edf 100644 --- a/spec/frontend/admin/users/mock_data.js +++ b/spec/frontend/admin/users/mock_data.js @@ -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', diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index 7fc811c1ac6..480b1e2a0de 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/reports/security/scanner_spec.rb b/spec/lib/gitlab/ci/reports/security/scanner_spec.rb index 02bb72e2c67..99f5d4723d3 100644 --- a/spec/lib/gitlab/ci/reports/security/scanner_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/scanner_spec.rb @@ -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 diff --git a/spec/services/groups/group_links/destroy_service_spec.rb b/spec/services/groups/group_links/destroy_service_spec.rb index 97fe23e9147..e63adc07313 100644 --- a/spec/services/groups/group_links/destroy_service_spec.rb +++ b/spec/services/groups/group_links/destroy_service_spec.rb @@ -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 diff --git a/spec/services/groups/group_links/update_service_spec.rb b/spec/services/groups/group_links/update_service_spec.rb index 82c4a10f15a..31446c8e4bf 100644 --- a/spec/services/groups/group_links/update_service_spec.rb +++ b/spec/services/groups/group_links/update_service_spec.rb @@ -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 diff --git a/spec/support/helpers/features/admin_users_helpers.rb b/spec/support/helpers/features/admin_users_helpers.rb new file mode 100644 index 00000000000..99b19eedcff --- /dev/null +++ b/spec/support/helpers/features/admin_users_helpers.rb @@ -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 diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb index ff5f7b9db27..151bbb75226 100644 --- a/spec/workers/remove_expired_group_links_worker_spec.rb +++ b/spec/workers/remove_expired_group_links_worker_spec.rb @@ -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) |