diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-10 00:07:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-10 00:07:53 +0300 |
commit | b558e1ad8f1d2d52e0c88ad712cd3d567cf52e10 (patch) | |
tree | 5fc6b81818c0af3aefd85816aa3026b26515615e | |
parent | b031a57ae71b1fc61782b891d2a31852ab87e7f3 (diff) |
Add latest changes from gitlab-org/gitlab@master
76 files changed, 875 insertions, 445 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index 6124befd3b6..82229b5aa8f 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -156,7 +156,7 @@ export default class ShortcutsIssuable extends Shortcuts { static copyBranchName() { // There are two buttons - one that is shown when the sidebar // is expanded, and one that is shown when it's collapsed. - const allCopyBtns = Array.from(document.querySelectorAll('.js-sidebar-source-branch button')); + const allCopyBtns = Array.from(document.querySelectorAll('.js-source-branch-copy')); // Select whichever button is currently visible so that // the "Copied" tooltip is shown when a click is simulated. diff --git a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue index 24cca8efa8e..7f313086fca 100644 --- a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue +++ b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue @@ -3,6 +3,7 @@ import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { mapGetters } from 'vuex'; import { __ } from '~/locale'; import { IssuableType, WorkspaceType } from '~/issues/constants'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; export default { @@ -15,6 +16,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagMixin()], inject: ['hidden'], computed: { ...mapGetters(['getNoteableData']), @@ -24,6 +26,9 @@ export default { isConfidential() { return this.getNoteableData.confidential; }, + isMergeRequest() { + return this.getNoteableData.targetType === 'merge_request' && this.glFeatures.updatedMrHeader; + }, warningIconsMeta() { return [ { @@ -58,7 +63,8 @@ export default { v-gl-tooltip :data-testid="meta.dataTestId" :title="meta.tooltip || null" - class="issuable-warning-icon inline" + :class="{ 'gl-mr-3 gl-mt-2': isMergeRequest }" + class="issuable-warning-icon gl-display-flex gl-justify-content-center gl-align-items-center" > <gl-icon :name="meta.iconName" class="icon" /> </div> diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue index bd6fdc131cb..8aaf42ce3da 100644 --- a/app/assets/javascripts/issuable/components/status_box.vue +++ b/app/assets/javascripts/issuable/components/status_box.vue @@ -1,6 +1,7 @@ <script> import { GlIcon } from '@gitlab/ui'; import Vue from 'vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { fetchPolicies } from '~/lib/graphql'; import { __ } from '~/locale'; @@ -11,9 +12,12 @@ export const statusBoxState = Vue.observable({ const CLASSES = { opened: 'status-box-open', + merge_request_opened: 'badge-success', locked: 'status-box-open', + merge_request_locked: 'badge-success', closed: 'status-box-mr-closed', - merged: 'status-box-mr-merged', + merge_request_closed: 'badge-danger', + merged: 'badge-info', }; const STATUS = { @@ -27,6 +31,7 @@ export default { components: { GlIcon, }, + mixins: [glFeatureFlagMixin()], inject: { query: { default: null }, projectPath: { default: null }, @@ -52,8 +57,17 @@ export default { return statusBoxState; }, computed: { + isMergeRequest() { + return this.issuableType === 'merge_request' && this.glFeatures.updatedMrHeader; + }, statusBoxClass() { - return CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state]; + return [ + CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state], + { + 'badge badge-pill gl-badge gl-mr-3': this.isMergeRequest, + 'issuable-status-box status-box': !this.isMergeRequest, + }, + ]; }, statusHumanName() { return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[0]; @@ -90,9 +104,13 @@ export default { </script> <template> - <div :class="statusBoxClass" class="issuable-status-box status-box"> - <gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" /> - <span class="gl-display-none gl-sm-display-block"> + <div :class="statusBoxClass"> + <gl-icon + v-if="!isMergeRequest" + :name="statusIconName" + class="gl-display-block gl-sm-display-none!" + /> + <span :class="{ 'gl-display-none gl-sm-display-block': !isMergeRequest }"> {{ statusHumanName }} </span> </div> diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index b58c9cbc496..960b25bb552 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -32,8 +32,16 @@ function MergeRequest(opts) { selector: '.detail-page-description', lockVersion: this.$el.data('lockVersion'), onSuccess: (result) => { - document.querySelector('#task_status').innerText = result.task_status; - document.querySelector('#task_status_short').innerText = result.task_status_short; + const taskStatus = document.querySelector('#task_status'); + const taskStatusShort = document.querySelector('#task_status_short'); + + if (taskStatus) { + taskStatus.innerText = result.task_status; + } + + if (taskStatusShort) { + document.querySelector('#task_status_short').innerText = result.task_status_short; + } }, onError: () => { createFlash({ @@ -148,7 +156,11 @@ MergeRequest.toggleDraftStatus = function (title, isReady) { } else { toast(__('Marked as draft. Can only be merged when marked as ready.')); } - const titleEl = document.querySelector('.merge-request .detail-page-description .title'); + const titleEl = document.querySelector( + `.merge-request .detail-page-${ + window.gon?.features?.updatedMrHeader ? 'header' : 'description' + } .title`, + ); if (titleEl) { titleEl.textContent = title; diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js index ac32cf951f8..1b5d00662dc 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -36,6 +36,7 @@ export default function initMergeRequestShow() { return h(StatusBox, { props: { initialState: el.dataset.state, + issuableType: 'merge_request', }, }); }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue index 01d8de132e7..456a1f17aae 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue @@ -49,7 +49,7 @@ export default { ]" class="gl-rounded-full gl-mr-3 gl-relative gl-p-2" > - <gl-loading-icon v-if="isLoading" size="md" inline class="gl-display-block" /> + <gl-loading-icon v-if="isLoading" size="lg" inline class="gl-display-block" /> <gl-icon v-else :name="$options.EXTENSION_ICON_NAMES[iconName]" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue index 2cef37d5c2e..b8a1f89d232 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -1,5 +1,5 @@ <script> -import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui'; +import { GlSafeHtmlDirective as SafeHtml, GlLink, GlSprintf } from '@gitlab/ui'; import { s__, n__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -10,6 +10,7 @@ export default { }, components: { GlLink, + GlSprintf, }, mixins: [glFeatureFlagMixin()], props: { @@ -28,6 +29,16 @@ export default { required: false, default: true, }, + divergedCommitsCount: { + type: Number, + required: false, + default: 0, + }, + targetBranchPath: { + type: String, + required: false, + default: '', + }, }, computed: { closesText() { @@ -81,5 +92,19 @@ export default { }}</gl-link> </span> </p> + <div + v-if=" + divergedCommitsCount > 0 && glFeatures.updatedMrHeader && !glFeatures.restructuredMrWidget + " + class="diverged-commits-count" + > + <gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')"> + <template #link> + <gl-link :href="targetBranchPath">{{ + n__('%d commit behind', '%d commits behind', divergedCommitsCount) + }}</gl-link> + </template> + </gl-sprintf> + </div> </section> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index f12345426d4..87fd06ed233 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -716,6 +716,20 @@ export default { {{ __('Merge details') }} </strong> <ul class="gl-pl-4 gl-m-0"> + <li + v-if="mr.divergedCommitsCount > 0 && glFeatures.updatedMrHeader" + class="gl-line-height-normal" + > + <gl-sprintf + :message="s__('mrWidget|The source branch is %{link} the target branch')" + > + <template #link> + <gl-link :href="mr.targetBranchPath">{{ + n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount) + }}</gl-link> + </template> + </gl-sprintf> + </li> <li class="gl-line-height-normal"> <added-commit-message :state="mr.state" @@ -756,6 +770,8 @@ export default { :state="mr.state" :related-links="mr.relatedLinks" :show-assign-to-me="false" + :diverged-commits-count="mr.divergedCommitsCount" + :target-branch-path="mr.targetBranchPath" class="mr-ready-merge-related-links gl-display-inline" /> </template> @@ -769,6 +785,22 @@ export default { > {{ __('The latest pipeline for this merge request did not complete successfully.') }} </div> + <div + v-if=" + mr.divergedCommitsCount > 0 && + glFeatures.updatedMrHeader && + !glFeatures.restructuredMrWidget + " + class="diverged-commits-count gl-mt-4" + > + <gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')"> + <template #link> + <gl-link :href="mr.targetBranchPath">{{ + n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount) + }}</gl-link> + </template> + </gl-sprintf> + </div> </div> </div> <template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget"> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 04f71e2b185..8b4f13b4f92 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -165,7 +165,12 @@ export default { return this.mr?.codequalityReportsPath; }, shouldRenderRelatedLinks() { - return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState; + const showDivergedCounts = + this.mr.divergedCommitsCount > 0 && this.mr.state !== 'readyToMerge'; + + return ( + (Boolean(this.mr.relatedLinks) || showDivergedCounts) && !this.mr.isNothingToMergeState + ); }, shouldRenderSourceBranchRemovalStatus() { return ( @@ -231,6 +236,9 @@ export default { isRestructuredMrWidgetEnabled() { return window.gon?.features?.restructuredMrWidget; }, + isUpdatedHeaderEnabled() { + return window.gon?.features?.updatedMrHeader; + }, }, watch: { 'mr.machineValue': { @@ -524,11 +532,15 @@ export default { </script> <template> <div v-if="isLoaded" class="mr-state-widget gl-mt-3"> - <header class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100"> + <header + v-if="shouldRenderCollaborationStatus || !isUpdatedHeaderEnabled" + :class="{ 'mr-widget-workflow gl-mt-0!': isUpdatedHeaderEnabled }" + class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100" + > <mr-widget-alert-message v-if="shouldRenderCollaborationStatus" type="info"> {{ s__('mrWidget|Members who can merge are allowed to add commits.') }} </mr-widget-alert-message> - <mr-widget-header :mr="mr" /> + <mr-widget-header v-if="!isUpdatedHeaderEnabled" :mr="mr" /> </header> <mr-widget-suggest-pipeline v-if="shouldSuggestPipelines" @@ -620,6 +632,8 @@ export default { v-if="shouldRenderRelatedLinks" :state="mr.state" :related-links="mr.relatedLinks" + :diverged-commits-count="mr.divergedCommitsCount" + :target-branch-path="mr.targetBranchPath" class="mr-info-list gl-ml-7 gl-pb-5" /> diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue index 446fffd4633..840911dc99c 100644 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue +++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue @@ -140,7 +140,7 @@ export default { <gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content"> <gl-loading-icon v-if="isLoading" - size="md" + size="lg" class="gl-absolute gl-left-0 gl-top-0 gl-right-0" /> <template v-else> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue index 12daaea8758..ddfd7e66bcb 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue @@ -175,7 +175,7 @@ export default { :debounce="300" /> <div data-testid="content" class="dropdown-content"> - <gl-loading-icon v-if="projectsListLoading" size="md" class="gl-p-5" /> + <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" /> <ul v-else> <gl-dropdown-item v-for="project in projects" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue index 623e7799493..134575b7a27 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue @@ -185,7 +185,7 @@ export default { <gl-loading-icon v-if="labelsFetchInProgress" class="labels-fetch-loading gl-align-items-center w-100 h-100" - size="md" + size="lg" /> <ul v-else class="list-unstyled gl-mb-0 gl-word-break-word"> <label-item diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue index ae179ef93c7..f595e635f2c 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue @@ -147,7 +147,7 @@ export default { <gl-loading-icon v-if="labelsFetchInProgress" class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3" - size="md" + size="lg" /> <template v-else> <gl-dropdown-item diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue index 9df5254155e..91f20863089 100644 --- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue +++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue @@ -298,7 +298,7 @@ export default { <gl-loading-icon v-if="isLoading" data-testid="loading-participants" - size="md" + size="lg" class="gl-absolute gl-left-0 gl-top-0 gl-right-0" /> <template v-else> diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue index 2525e11d6de..04c6a61689c 100644 --- a/app/assets/javascripts/work_items/pages/create_work_item.vue +++ b/app/assets/javascripts/work_items/pages/create_work_item.vue @@ -192,7 +192,7 @@ export default { <div> <gl-loading-icon v-if="$apollo.queries.workItemTypes.loading" - size="md" + size="lg" data-testid="loading-types" /> <gl-form-select diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 2d66b44ed13..bd5888af3ad 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -666,12 +666,12 @@ $tabs-holder-z-index: 250; margin-top: $gl-padding; position: relative; - &::before { + &:not(:last-child)::before { content: ''; border-left: 1px solid var(--gray-100, $gray-100); position: absolute; left: 28px; - top: -17px; + bottom: -17px; height: 16px; } } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index f237d57aa88..bc9975ea17a 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -8,7 +8,8 @@ a { color: $gl-text-color; - &.link { + &.link, + &.gl-link { color: $blue-600; } } @@ -38,9 +39,18 @@ align-self: center; flex: 0 0 auto; - @include media-breakpoint-down(xs) { - width: 100%; - margin-top: 10px; + &:not(.is-merge-request) { + @include media-breakpoint-down(xs) { + width: 100%; + margin-top: 10px; + } + } + + &.is-merge-request { + @include media-breakpoint-down(sm) { + width: 100%; + margin-top: 10px; + } } } @@ -56,4 +66,8 @@ .description { margin-top: 6px; } + + .author-link { + color: $gl-text-color; + } } diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9323f820ba5..d127f688dc5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -45,6 +45,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:realtime_labels, project) push_frontend_feature_flag(:updated_diff_expansion_buttons, project) push_frontend_feature_flag(:mr_attention_requests, current_user) + push_frontend_feature_flag(:updated_mr_header, project) end before_action do diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 6d17c8b5b42..5c25c608819 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -28,16 +28,18 @@ module IssuesHelper end def status_box_class(item) + updated_mr_header_enabled = Feature.enabled?(:updated_mr_header, @project) + if item.try(:expired?) 'status-box-expired' elsif item.try(:merged?) - 'status-box-mr-merged' + updated_mr_header_enabled ? 'badge-info' : 'status-box-mr-merged' elsif item.closed? - 'status-box-mr-closed' + item.is_a?(MergeRequest) && updated_mr_header_enabled ? 'badge-danger' : 'status-box-mr-closed' elsif item.try(:upcoming?) 'status-box-upcoming' else - 'status-box-open' + item.is_a?(MergeRequest) && updated_mr_header_enabled ? 'badge-success' : 'status-box-open' end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index f70afcc5791..6eaeaf1b025 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -232,6 +232,30 @@ module MergeRequestsHelper def default_suggestion_commit_message @project.suggestion_commit_message.presence || Gitlab::Suggestions::CommitMessage::DEFAULT_SUGGESTION_COMMIT_MESSAGE end + + def merge_request_source_branch(merge_request) + branch = if merge_request.for_fork? + "#{merge_request.source_project_path}:#{merge_request.source_branch}" + else + merge_request.source_branch + end + + branch_path = if merge_request.source_project + project_tree_path(merge_request.source_project, merge_request.source_branch) + else + '' + end + + link_to branch, branch_path, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2' + end + + def merge_request_header(project, merge_request) + link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold', avatar: false) + copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy') + target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2' + + _('%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe, span_start: '<span class="gl-display-inline-block">'.html_safe, span_end: '</span>'.html_safe } + end end MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper') diff --git a/app/views/admin/sessions/_new_base.html.haml b/app/views/admin/sessions/_new_base.html.haml index c9b002a4dd2..65eb1358b40 100644 --- a/app/views/admin/sessions/_new_base.html.haml +++ b/app/views/admin/sessions/_new_base.html.haml @@ -4,4 +4,4 @@ = password_field_tag 'user[password]', nil, class: 'form-control', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' } .submit-container.move-submit-down - = submit_tag _('Enter Admin Mode'), class: 'gl-button btn btn-success', data: { qa_selector: 'enter_admin_mode_button' } + = submit_tag _('Enter Admin Mode'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'enter_admin_mode_button' } diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml index ab7eb8c79dc..9372bae14c3 100644 --- a/app/views/admin/sessions/_signin_box.html.haml +++ b/app/views/admin/sessions/_signin_box.html.haml @@ -14,6 +14,6 @@ = render_if_exists 'devise/sessions/new_smartcard' - if allow_admin_mode_password_authentication_for_web? - .login-box.tab-pane{ id: 'login-pane', role: 'tabpanel', class: active_when(!any_form_based_providers_enabled?) } + .login-box.tab-pane.gl-p-5{ id: 'login-pane', role: 'tabpanel', class: active_when(!any_form_based_providers_enabled?) } .login-body = render 'admin/sessions/new_base' diff --git a/app/views/admin/sessions/_two_factor_otp.html.haml b/app/views/admin/sessions/_two_factor_otp.html.haml index 3fe6e20a367..40ba79d1a65 100644 --- a/app/views/admin/sessions/_two_factor_otp.html.haml +++ b/app/views/admin/sessions/_two_factor_otp.html.haml @@ -6,4 +6,4 @@ = _("Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.") .submit-container.move-submit-down - = submit_tag 'Verify code', class: 'gl-button btn btn-success' + = submit_tag 'Verify code', class: 'gl-button btn btn-confirm' diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml index 67c607270a5..7d07b49c98e 100644 --- a/app/views/admin/sessions/new.html.haml +++ b/app/views/admin/sessions/new.html.haml @@ -8,7 +8,7 @@ - if any_form_based_providers_enabled? = render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false - else - = render 'devise/shared/tabs_normal', tab_title: _('Enter Admin Mode'), render_signup_link: false + = render 'devise/shared/tab_single', tab_title: page_title .tab-content - if allow_admin_mode_password_authentication_for_web? || ldap_sign_in_enabled? || crowd_enabled? = render 'admin/sessions/signin_box' diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml index 531ab206157..3f915846dd8 100644 --- a/app/views/admin/sessions/two_factor.html.haml +++ b/app/views/admin/sessions/two_factor.html.haml @@ -5,9 +5,9 @@ .col-md-5.new-session-forms-container .login-page #signin-container - = render 'devise/shared/tabs_normal', tab_title: _('Enter Admin Mode'), render_signup_link: false + = render 'devise/shared/tab_single', tab_title: _('Enter Admin Mode') .tab-content - .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } + .login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' } .login-body - if current_user.two_factor_otp_enabled? = render 'admin/sessions/two_factor_otp' diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 1b5a932a09a..336954d00b0 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,2 +1,2 @@ = gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do - = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1' } + = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { qa_selector: 'sign_in_tab' } } diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml deleted file mode 100644 index 01dd3748887..00000000000 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- tab_title = local_assigns.fetch(:tab_title, _('Sign in')) -- render_signup_link = local_assigns.fetch(:render_signup_link, true) - -%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' } - %li.nav-item{ role: 'presentation' } - %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' }= tab_title - - if render_signup_link && allow_signup? - %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_action: 'click_button', track_value: '', toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' } Register diff --git a/app/views/projects/feature_flags/edit.html.haml b/app/views/projects/feature_flags/edit.html.haml index ac8c0575077..121dcd31a13 100644 --- a/app/views/projects/feature_flags/edit.html.haml +++ b/app/views/projects/feature_flags/edit.html.haml @@ -2,6 +2,6 @@ - add_to_breadcrumbs s_('FeatureFlags|Feature Flags'), project_feature_flags_path(@project) - breadcrumb_title @feature_flag.name -- page_title s_('FeatureFlags|Edit Feature Flag') +- page_title s_('FeatureFlags|Edit Feature Flag'), @feature_flag.name #js-edit-feature-flag{ data: edit_feature_flag_data } diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml index 916b841e350..f4c7be14759 100644 --- a/app/views/projects/merge_requests/_mr_box.html.haml +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -1,3 +1,9 @@ .detail-page-description.py-2 - %h2.title.mb-0{ data: { qa_selector: 'title_content' } } - = markdown_field(@merge_request, :title) + - if Feature.enabled?(:updated_mr_header, @project) + - state_human_name, _ = state_name_with_icon(@merge_request) + .badge.badge-pill.gl-badge.gl-mr-3.js-mr-status-box{ class: status_box_class(@merge_request), data: { project_path: @merge_request.project.path_with_namespace, iid: @merge_request.iid, state: @merge_request.state } }> + = state_human_name + = merge_request_header(@project, @merge_request) + - else + %h2.title.mb-0{ data: { qa_selector: 'title_content' } } + = markdown_field(@merge_request, :title) diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml index 488c49736e6..3cd366d5bac 100644 --- a/app/views/projects/merge_requests/_mr_title.html.haml +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -3,6 +3,7 @@ - can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request) - are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false) - cache_key = [@project, @merge_request, can_update_merge_request, can_reopen_merge_request, are_close_and_open_buttons_hidden, current_user&.preferred_language] +- moved_sidebar_enabled = Feature.enabled?(:updated_mr_header, @project) = cache(cache_key, expires_in: 1.day) do - if @merge_request.closed_or_merged_without_fork? @@ -12,18 +13,24 @@ = c.body do = _('The source project of this merge request has been removed.') - .detail-page-header.border-bottom-0.pt-0.pb-0 + .detail-page-header.border-bottom-0.pt-0.pb-0{ class: "#{'gl-display-block gl-md-display-flex!' if moved_sidebar_enabled}" } .detail-page-header-body - = render "shared/issuable/status_box", issuable: @merge_request + - unless moved_sidebar_enabled + = render "shared/issuable/status_box", issuable: @merge_request + .issuable-meta{ class: "#{'gl-display-flex' if moved_sidebar_enabled}" } + - if moved_sidebar_enabled + #js-issuable-header-warnings + %h2.title.gl-my-0.gl-display-inline-block{ data: { qa_selector: 'title_content' } } + = markdown_field(@merge_request, :title) + - else + #js-issuable-header-warnings + = issuable_meta(@merge_request, @project) - .issuable-meta - #js-issuable-header-warnings - = issuable_meta(@merge_request, @project) + %div + %button.gl-button.btn.btn-default.btn-icon.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ type: 'button' } + = sprite_icon('chevron-double-lg-left') - %a.gl-button.btn.btn-default.btn-icon.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } - = sprite_icon('chevron-double-lg-left') - - .detail-page-header-actions.js-issuable-actions + .detail-page-header-actions.js-issuable-actions{ class: "#{'gl-align-self-start is-merge-request' if moved_sidebar_enabled}" } - if @merge_request.source_project = render 'projects/merge_requests/code_dropdown' diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml index 7289121d9eb..2e04bbf3605 100644 --- a/app/views/shared/deploy_tokens/_form.html.haml +++ b/app/views/shared/deploy_tokens/_form.html.haml @@ -3,7 +3,7 @@ - group_deploy_tokens_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_deploy_tokens_help_link_url } = s_('DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}').html_safe % { link_start: group_deploy_tokens_help_link_start, link_end: '</a>'.html_safe } -= form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f| += gitlab_ui_form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f| .form-group = f.label :name, class: 'label-bold' @@ -23,33 +23,15 @@ .form-group = f.label :scopes, _('Scopes (select at least one)'), class: 'label-bold' - %fieldset.form-group.form-check - = f.check_box :read_repository, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_repository_checkbox' } - = f.label :read_repository, 'read_repository', class: 'label-bold form-check-label' - .text-secondary - = s_('DeployTokens|Allows read-only access to the repository.') + = f.gitlab_ui_checkbox_component :read_repository, 'read_repository', help_text: s_('DeployTokens|Allows read-only access to the repository.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_repository_checkbox' } } - if container_registry_enabled?(group_or_project) - %fieldset.form-group.form-check - = f.check_box :read_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_registry_checkbox' } - = f.label :read_registry, 'read_registry', class: 'label-bold form-check-label' - .text-secondary= s_('DeployTokens|Allows read-only access to registry images.') - - %fieldset.form-group.form-check - = f.check_box :write_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_write_registry_checkbox' } - = f.label :write_registry, 'write_registry', class: 'label-bold form-check-label' - .text-secondary= s_('DeployTokens|Allows write access to registry images.') + = f.gitlab_ui_checkbox_component :read_registry, 'read_registry', help_text: s_('DeployTokens|Allows read-only access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_registry_checkbox' } } + = f.gitlab_ui_checkbox_component :write_registry, 'write_registry', help_text: s_('DeployTokens|Allows write access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_registry_checkbox' } } - if packages_registry_enabled?(group_or_project) - %fieldset.form-group.form-check - = f.check_box :read_package_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_package_registry_checkbox' } - = f.label :read_package_registry, 'read_package_registry', class: 'label-bold form-check-label' - .text-secondary= s_('DeployTokens|Allows read-only access to the package registry.') - - %fieldset.form-group.form-check - = f.check_box :write_package_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_write_package_registry_checkbox' } - = f.label :write_package_registry, 'write_package_registry', class: 'label-bold form-check-label' - .text-secondary= s_('DeployTokens|Allows read and write access to the package registry.') + = f.gitlab_ui_checkbox_component :read_package_registry, 'read_package_registry', help_text: s_('DeployTokens|Allows read-only access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_package_registry_checkbox' } } + = f.gitlab_ui_checkbox_component :write_package_registry, 'write_package_registry', help_text: s_('DeployTokens|Allows read and write access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_package_registry_checkbox' } } .gl-mt-3 = f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-confirm', data: { qa_selector: 'create_deploy_token_button' } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index da1c15f7e70..bb6cc693ad9 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -86,7 +86,7 @@ - if issuable_type == 'merge_request' .sub-block.js-sidebar-source-branch .sidebar-collapsed-icon.js-dont-change-state - = clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport') + = clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport', class: 'btn-clipboard gl-button btn-default-tertiary btn-icon btn-sm js-source-branch-copy') .gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed %span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-white-space-nowrap = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' data-testid='ref-name' title='#{html_escape(source_branch)}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: html_escape(source_branch) } diff --git a/config/feature_flags/development/updated_mr_header.yml b/config/feature_flags/development/updated_mr_header.yml new file mode 100644 index 00000000000..b5ed19b65dc --- /dev/null +++ b/config/feature_flags/development/updated_mr_header.yml @@ -0,0 +1,8 @@ +--- +name: updated_mr_header +introduced_by_url: +rollout_issue_url: +milestone: '14.10' +type: development +group: group::code review +default_enabled: false diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index dda56c31fd0..f231ecbea29 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -288,7 +288,6 @@ production: &base # object_store: # enabled: false # remote_directory: artifacts # The bucket name - # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage # connection: # provider: AWS # Only AWS supported at the moment @@ -308,7 +307,6 @@ production: &base # object_store: # enabled: false # remote_directory: external-diffs - # background_upload: false # proxy_download: false # connection: # provider: AWS @@ -324,8 +322,6 @@ production: &base object_store: enabled: false remote_directory: lfs-objects # Bucket name - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) - # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage connection: provider: AWS @@ -346,8 +342,6 @@ production: &base object_store: enabled: false remote_directory: uploads # Bucket name - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) - # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage connection: provider: AWS @@ -368,8 +362,6 @@ production: &base object_store: enabled: false remote_directory: packages # The bucket name - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) - # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage connection: provider: AWS @@ -389,8 +381,6 @@ production: &base object_store: enabled: false remote_directory: dependency_proxy # The bucket name - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) - # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage connection: provider: AWS @@ -1419,7 +1409,6 @@ test: object_store: enabled: false remote_directory: artifacts # The bucket name - background_upload: false connection: provider: AWS # Only AWS supported at the moment aws_access_key_id: AWS_ACCESS_KEY_ID diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index ea6a00b395f..454969a10cf 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -252,7 +252,7 @@ Settings.gitlab_ci['url'] ||= Settings.__send__(:build_gitlab_ci Settings['ci_secure_files'] ||= Settingslogic.new({}) Settings.ci_secure_files['enabled'] = true if Settings.ci_secure_files['enabled'].nil? Settings.ci_secure_files['storage_path'] = Settings.absolute(Settings.ci_secure_files['storage_path'] || File.join(Settings.shared['path'], "ci_secure_files")) -Settings.ci_secure_files['object_store'] = ObjectStoreSettings.legacy_parse(Settings.ci_secure_files['object_store']) +Settings.ci_secure_files['object_store'] = ObjectStoreSettings.legacy_parse(Settings.ci_secure_files['object_store'], 'secure_files') # # Reply by email @@ -276,7 +276,7 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values # Settings.artifact['path'] is deprecated, use `storage_path` instead Settings.artifacts['path'] = Settings.artifacts['storage_path'] Settings.artifacts['max_size'] ||= 100 # in megabytes -Settings.artifacts['object_store'] = ObjectStoreSettings.legacy_parse(Settings.artifacts['object_store']) +Settings.artifacts['object_store'] = ObjectStoreSettings.legacy_parse(Settings.artifacts['object_store'], 'artifacts') # # Registry @@ -321,7 +321,7 @@ Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_secret') # We want pages zip archives to be stored on the same directory as old pages hierarchical structure # this will allow us to easier migrate existing instances with NFS Settings.pages['storage_path'] = Settings.pages['path'] -Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store']) +Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store'], 'pages') Settings.pages['local_store'] ||= Settingslogic.new({}) Settings.pages['local_store']['path'] = Settings.absolute(Settings.pages['local_store']['path'] || File.join(Settings.shared['path'], "pages")) Settings.pages['local_store']['enabled'] = true if Settings.pages['local_store']['enabled'].nil? @@ -362,7 +362,7 @@ Settings['external_diffs'] ||= Settingslogic.new({}) Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil? Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil? Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs')) -Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.external_diffs['object_store']) +Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.external_diffs['object_store'], 'external_diffs') # # Git LFS @@ -370,7 +370,7 @@ Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Setti Settings['lfs'] ||= Settingslogic.new({}) Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil? Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects")) -Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['object_store']) +Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['object_store'], 'lfs') # # Uploads @@ -378,7 +378,7 @@ Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['ob Settings['uploads'] ||= Settingslogic.new({}) Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public') Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system' -Settings.uploads['object_store'] = ObjectStoreSettings.legacy_parse(Settings.uploads['object_store']) +Settings.uploads['object_store'] = ObjectStoreSettings.legacy_parse(Settings.uploads['object_store'], 'uploads') Settings.uploads['object_store']['remote_directory'] ||= 'uploads' # @@ -388,7 +388,7 @@ Settings['packages'] ||= Settingslogic.new({}) Settings.packages['enabled'] = true if Settings.packages['enabled'].nil? Settings.packages['dpkg_deb_path'] = '/usr/bin/dpkg-deb' if Settings.packages['dpkg_deb_path'].nil? Settings.packages['storage_path'] = Settings.absolute(Settings.packages['storage_path'] || File.join(Settings.shared['path'], "packages")) -Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.packages['object_store']) +Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.packages['object_store'], 'packages') # # Dependency Proxy @@ -396,7 +396,7 @@ Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.p Settings['dependency_proxy'] ||= Settingslogic.new({}) Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil? Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy")) -Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store']) +Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'], 'dependency_proxy') # For first iteration dependency proxy uses Rails server to download blobs. # To ensure acceptable performance we only allow feature to be used with @@ -410,7 +410,7 @@ Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma? Settings['terraform_state'] ||= Settingslogic.new({}) Settings.terraform_state['enabled'] = true if Settings.terraform_state['enabled'].nil? Settings.terraform_state['storage_path'] = Settings.absolute(Settings.terraform_state['storage_path'] || File.join(Settings.shared['path'], "terraform_state")) -Settings.terraform_state['object_store'] = ObjectStoreSettings.legacy_parse(Settings.terraform_state['object_store']) +Settings.terraform_state['object_store'] = ObjectStoreSettings.legacy_parse(Settings.terraform_state['object_store'], 'terraform_state') # # Mattermost diff --git a/config/object_store_settings.rb b/config/object_store_settings.rb index 53fbfb088db..ea954b7061a 100644 --- a/config/object_store_settings.rb +++ b/config/object_store_settings.rb @@ -16,15 +16,26 @@ class ObjectStoreSettings # we don't need to raise an error in that case ALLOWED_INCOMPLETE_TYPES = %w(pages).freeze + # A fallback switch in case anyone gets a trouble with background upload removal + # Epic: https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/734 + LEGACY_BACKGROUND_UPLOADS_ENV = "GITLAB_LEGACY_BACKGROUND_UPLOADS" + attr_accessor :settings # Legacy parser - def self.legacy_parse(object_store) + def self.legacy_parse(object_store, object_store_type) object_store ||= Settingslogic.new({}) object_store['enabled'] = false if object_store['enabled'].nil? object_store['remote_directory'] ||= nil - object_store['direct_upload'] = false if object_store['direct_upload'].nil? - object_store['background_upload'] = true if object_store['background_upload'].nil? + + if support_legacy_background_upload?(object_store_type) + object_store['direct_upload'] = false + object_store['background_upload'] = true + else + object_store['direct_upload'] = true + object_store['background_upload'] = false + end + object_store['proxy_download'] = false if object_store['proxy_download'].nil? object_store['storage_options'] ||= {} @@ -33,6 +44,10 @@ class ObjectStoreSettings object_store end + def self.support_legacy_background_upload?(object_store_type) + ENV[LEGACY_BACKGROUND_UPLOADS_ENV].to_s.split(',').map(&:strip).include?(object_store_type) + end + def initialize(settings) @settings = settings end diff --git a/data/removals/15_0/15-0-sidekiq-metrics-health-check-config.yml b/data/removals/15_0/15-0-sidekiq-metrics-health-check-config.yml new file mode 100644 index 00000000000..cfd0d5f5b13 --- /dev/null +++ b/data/removals/15_0/15-0-sidekiq-metrics-health-check-config.yml @@ -0,0 +1,24 @@ +- name: "Sidekiq configuration for metrics and health checks" + announcement_milestone: "14.7" + announcement_date: "2021-01-22" + removal_milestone: "15.0" + removal_date: "2022-05-22" + breaking_change: true + body: | # Do not modify this line, instead modify the lines below. + In GitLab 15.0, you can no longer serve Sidekiq metrics and health checks over a single address and port. + + To improve stability, availability, and prevent data loss in edge cases, GitLab now serves + [Sidekiq metrics and health checks from two separate servers](https://gitlab.com/groups/gitlab-org/-/epics/6409). + + When you use Omnibus or Helm charts, if GitLab is configured for both servers to bind to the same address, + a configuration error occurs. + To prevent this error, choose different ports for the metrics and health check servers: + + - [Configure Sidekiq health checks](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-health-checks) + - [Configure the Sidekiq metrics server](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-the-sidekiq-metrics-server) + + If you installed GitLab from source, verify manually that both servers are configured to bind to separate addresses and ports. + stage: Enablement + tiers: [Free, Premium, Ultimate] + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347509 + documentation_url: https://docs.gitlab.com/ee/administration/sidekiq.html diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 4cee2673225..2582df1b0c4 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -113,8 +113,6 @@ and then `object_store:`. On Omnibus GitLab installs they are prefixed by |---------------------|---------|-------------| | `enabled` | `false` | Enable or disable object storage. | | `remote_directory` | | The bucket name where Artifacts are stored. Use the name only, do not include the path. | -| `direct_upload` | `false` | Set to `true` to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | -| `background_upload` | `true` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3. | | `proxy_download` | `false` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data. | | `connection` | | Various connection options described below. | @@ -549,7 +547,7 @@ Bucket names that include folder paths are not supported with [consolidated obje For example, `bucket/path`. If a bucket name has a path in it, you might receive an error similar to: ```plaintext -WARNING: Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/job_id/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+day: 500 Internal Server Error (Missing file) +WARNING: Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/job_id/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+day: 500 Internal Server Error (Missing file) FATAL: invalid argument ``` diff --git a/doc/administration/lfs/index.md b/doc/administration/lfs/index.md index 3fe6a94ef13..0d75880bdd1 100644 --- a/doc/administration/lfs/index.md +++ b/doc/administration/lfs/index.md @@ -89,8 +89,6 @@ The following general settings are supported. |---------------------|-------------|---------| | `enabled` | Enable/disable object storage. | `false` | | `remote_directory` | The bucket name where LFS objects are stored. | | -| `direct_upload` | Set to true to enable direct upload of LFS without the need of local shared storage. Option may be removed after we decide to support only single storage for all files. | `false` | -| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3. | `true` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data. | `false` | | `connection` | Various connection options described below. | | diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md index 01576eb4abf..fe1c74b0e24 100644 --- a/doc/administration/merge_request_diffs.md +++ b/doc/administration/merge_request_diffs.md @@ -117,8 +117,6 @@ then `object_store:`. On Omnibus installations, they are prefixed by |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where external diffs are stored| | -| `direct_upload` | Set to `true` to enable direct upload of external diffs without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | -| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md index 8ff5157e4d8..40c1b9d795a 100644 --- a/doc/administration/packages/dependency_proxy.md +++ b/doc/administration/packages/dependency_proxy.md @@ -140,8 +140,6 @@ This section describes the earlier configuration format. gitlab_rails['dependency_proxy_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/dependency_proxy" gitlab_rails['dependency_proxy_object_store_enabled'] = true gitlab_rails['dependency_proxy_object_store_remote_directory'] = "dependency_proxy" # The bucket name. - gitlab_rails['dependency_proxy_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false). - gitlab_rails['dependency_proxy_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true). gitlab_rails['dependency_proxy_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage. gitlab_rails['dependency_proxy_object_store_connection'] = { ## @@ -177,8 +175,6 @@ This section describes the earlier configuration format. object_store: enabled: false remote_directory: dependency_proxy # The bucket name. - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false). - # background_upload: true # Temporary option to limit automatic upload (Default: true). # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage. connection: ## diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md index b122cb9db90..ef00127a70e 100644 --- a/doc/administration/packages/index.md +++ b/doc/administration/packages/index.md @@ -152,8 +152,6 @@ We recommend using the [consolidated object storage settings](../object_storage. gitlab_rails['packages_enabled'] = true gitlab_rails['packages_object_store_enabled'] = true gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name. - gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false). - gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true). gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage. gitlab_rails['packages_object_store_connection'] = { ## @@ -192,8 +190,6 @@ We recommend using the [consolidated object storage settings](../object_storage. object_store: enabled: false remote_directory: packages # The bucket name. - # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false). - # background_upload: true # Temporary option to limit automatic upload (Default: true). # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage. connection: ## diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md index 692c5caec06..d66aa5945b6 100644 --- a/doc/administration/uploads.md +++ b/doc/administration/uploads.md @@ -67,8 +67,6 @@ For source installations the following settings are nested under `uploads:` and |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where Uploads will be stored| | -| `direct_upload` | Set to `true` to remove Puma from the Upload path. Workhorse handles the actual Artifact Upload to Object Storage while Puma does minimal processing to keep track of the upload. There is no need for local shared storage. The option may be removed if support for a single storage type for all files is introduced. Read more on [direct upload](../development/uploads/index.md#direct-upload). | `false` | -| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 (if `direct_upload` is set to `true` it will override `background_upload`) | `true` | | `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index 2c4c8af3b42..8a09745b675 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -14,8 +14,6 @@ Some variables are only available with more recent versions of [GitLab Runner](h You can [output the values of all variables available for a job](index.md#list-all-environment-variables) with a `script` command. -There are also [Kubernetes-specific deployment variables (deprecated)](../../user/project/clusters/deploy_to_cluster.md#deployment-variables). - There are also a number of [variables you can use to configure runner behavior](../runners/configure_runners.md#configure-runner-behavior-with-variables) globally or for individual jobs. | Variable | GitLab | Runner | Description | diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md index c6f59a7e452..8d88e7155a2 100644 --- a/doc/development/cicd/templates.md +++ b/doc/development/cicd/templates.md @@ -342,32 +342,6 @@ include: - remote: https://gitlab.com/gitlab-org/gitlab/-/raw/v13.0.1-ee/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml ``` -### Use a feature flag to roll out a `latest` template - -With a major version release like 13.0 or 14.0, [stable templates](#stable-version) must be -updated with their corresponding [latest template versions](#latest-version). -It may be hard to gauge the impact of this change, so use the `redirect_to_latest_template_<name>` -feature flag to test the impact on a subset of users. Using a feature flag can help -reduce the risk of reverts or rollbacks on production. - -For example, to redirect the stable `Jobs/Deploy` template to its latest template in 25% of -projects on `gitlab.com`: - -```shell -/chatops run feature set redirect_to_latest_template_jobs_deploy 25 --actors -``` - -After you're confident the latest template can be moved to stable: - -1. Update the stable template with the content of the latest version. -1. Remove the migration template from `Gitlab::Template::GitlabCiYmlTemplate::TEMPLATES_WITH_LATEST_VERSION` const. -1. Remove the corresponding feature flag. - -NOTE: -Feature flags are enabled by default in RSpec, so all tests are performed -against the latest templates. You should also test the stable templates -with `stub_feature_flags(redirect_to_latest_template_<name>: false)`. - ### Further reading There is an [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/17716) about diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md index 9a48f10bf06..b12f00009c8 100644 --- a/doc/development/dangerbot.md +++ b/doc/development/dangerbot.md @@ -196,10 +196,11 @@ is not shared to forks. Contributors can configure Danger for their forks with the following steps: -1. Add an [environment variable](../ci/variables/index.md) called `DANGER_GITLAB_API_TOKEN` with a -[personal API token](https://gitlab.com/-/profile/personal_access_tokens?name=GitLab+Dangerbot&scopes=api) -to your fork that has the `api` scope set. -1. Making the variable [masked](../ci/variables/index.md#mask-a-cicd-variable) makes sure it +1. Create a [personal API token](https://gitlab.com/-/profile/personal_access_tokens?name=GitLab+Dangerbot&scopes=api). +that has the `api` scope set (don't forget to copy it to the clipboard). +1. Add a [project CI/CD variable](../ci/variables/index.md#add-a-cicd-variable-to-a-project) +called `DANGER_GITLAB_API_TOKEN` with the token copied in the previous step. +1. Make the variable [masked](../ci/variables/index.md#mask-a-cicd-variable) so it doesn't show up in the job logs. The variable cannot be -[protected](../ci/variables/index.md#protected-cicd-variables), as it needs -to be present for all feature branches. +[protected](../ci/variables/index.md#protected-cicd-variables), because it needs +to be present for all branches. diff --git a/doc/update/removals.md b/doc/update/removals.md index d45828518c2..8dbfd363c6f 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -160,6 +160,28 @@ It also depends on a few third-party gems that are not actively maintained anymo For more information, check the [summary section of the deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352488#deprecation-summary). +### Sidekiq configuration for metrics and health checks + +WARNING: +This feature was changed or removed in 15.0 +as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes). +Before updating GitLab, review the details carefully to determine if you need to make any +changes to your code, settings, or workflow. + +In GitLab 15.0, you can no longer serve Sidekiq metrics and health checks over a single address and port. + +To improve stability, availability, and prevent data loss in edge cases, GitLab now serves +[Sidekiq metrics and health checks from two separate servers](https://gitlab.com/groups/gitlab-org/-/epics/6409). + +When you use Omnibus or Helm charts, if GitLab is configured for both servers to bind to the same address, +a configuration error occurs. +To prevent this error, choose different ports for the metrics and health check servers: + +- [Configure Sidekiq health checks](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-health-checks) +- [Configure the Sidekiq metrics server](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-the-sidekiq-metrics-server) + +If you installed GitLab from source, verify manually that both servers are configured to bind to separate addresses and ports. + ### Static Site Editor The Static Site Editor was deprecated in GitLab 14.7 and the feature is being removed in GitLab 15.0. Incoming requests to the Static Site Editor will be redirected and open the target file to edit in the Web IDE. Current users of the Static Site Editor can view the [documentation](https://docs.gitlab.com/ee/user/project/static_site_editor/) for more information, including how to remove the configuration files from existing projects. We will continue investing in improvements to the Markdown editing experience by [maturing the Content Editor](https://gitlab.com/groups/gitlab-org/-/epics/5401) and making it available as a way to edit content across GitLab. diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md index d612844d7ce..4c23e5d33d5 100644 --- a/doc/user/shortcuts.md +++ b/doc/user/shortcuts.md @@ -136,82 +136,82 @@ These shortcuts are available when browsing the files in a project (go to These shortcuts are available when editing a file with the [Web IDE](project/web_ide/index.md): -| macOS shortcut | Windows shortcut | Description | +| macOS shortcut | Windows/Linux shortcut | Description | |---------------------------------|---------------------|-------------| -| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>↑</kbd> | | Add cursor above | -| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>↓</kbd> | | Add cursor below | -| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>I</kbd> | | Add cursors to line ends | +| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>↑</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>↑</kbd> | Add cursor above | +| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>↓</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>↓</kbd> | Add cursor below | +| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>I</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>I</kbd> | Add cursors to line ends | | <kbd>Command</kbd> + <kbd>K</kbd>, <kbd>Command</kbd> + <kbd>C</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, <kbd>Control</kbd> + <kbd>C</kbd> _or_ <kbd>Control</kbd> + <kbd>/</kbd> | Add line comment | -| <kbd>Command</kbd> + <kbd>D</kbd> | | Add selection to next find match | -| <kbd>Command</kbd> + <kbd>F2</kbd> | | Change all occurrences | -| <kbd>F1</kbd> | | Command palette | -| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>↓</kbd> | | Copy line down | -| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>↑</kbd> | | Copy line up | -| <kbd>Command</kbd> + <kbd>U</kbd> | | Cursor undo | -| <kbd>Command</kbd> + <kbd>Backspace<kbd> | | Delete all left | +| <kbd>Command</kbd> + <kbd>D</kbd> | <kbd>Control</kbd> + <kbd>D</kbd> | Add selection to next find match | +| <kbd>Command</kbd> + <kbd>F2</kbd> | <kbd>Control</kbd> + <kbd>F2</kbd> | Change all occurrences | +| <kbd>F1</kbd> | <kbd>F1</kbd> | Command palette | +| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>↓</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>↓</kbd> | Copy line down | +| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>↑</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>↑</kbd> | Copy line up [(Linux note)](#linux-shortcuts) | +| <kbd>Command</kbd> + <kbd>U</kbd> | <kbd>Control</kbd> + <kbd>U</kbd> | Cursor undo | +| <kbd>Command</kbd> + <kbd>Backspace<kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Backspace</kbd> | Delete all left | | <kbd>Control</kbd> + <kbd>K</kbd> | | Delete all right | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>K</kbd> | | Delete line | -| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>→</kbd> | | Expand selection | -| <kbd>Command</kbd> + <kbd>P</kbd> | | File finder | -| <kbd>Command</kbd> + <kbd>F</kbd> | | Find | -| <kbd>Enter</kbd> | | Find next | -| <kbd>Command</kbd> + <kbd>F3</kbd> | | Find next selection | -| <kbd>Shift</kbd> + <kbd>Enter</kbd> + <kbd>F3</kbd> | | Find previous | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>F3</kbd> | | Find previous selection | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>K</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> | Delete line | +| | <kbd>Control</kbd> + <kbd>Backspace</kbd> | Delete word | +| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>→</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>→</kbd> | Expand selection | +| <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>P</kbd> | File finder | +| <kbd>Command</kbd> + <kbd>F</kbd> | <kbd>Control</kbd> + <kbd>F</kbd> | Find | +| <kbd>Enter</kbd> | <kbd>Enter</kbd> or <kbd>F3</kbd> | Find next | +| <kbd>Command</kbd> + <kbd>F3</kbd> | <kbd>F3</kbd> | Find next selection [(Linux note)](#linux-shortcuts) | +| <kbd>Shift</kbd> + <kbd>Enter</kbd> + <kbd>F3</kbd> | <kbd>Shift</kbd> + <kbd>F3</kbd> | Find previous | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>F3</kbd> | <kbd>Shift</kbd> + <kbd>F3</kbd> | Find previous selection | | <kbd>Command</kbd> + <kbd>E</kbd> | | Find with selection | -| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>[</kbd> | | Fold | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>O</kbd> | | Fold all | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>/</kbd> | | Fold all block comments | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>8</kbd> | | Fold all regions | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>-</kbd> | | Fold all regions except selected | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>1</kbd> | | Fold level 1 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>2</kbd> | | Fold level 2 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>3</kbd> | | Fold level 3 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>4</kbd> | | Fold level 4 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>5</kbd> | | Fold level 5 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>6</kbd> | | Fold level 6 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>7</kbd> | | Fold level 7 | -| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>[</kbd> | | Fold recursively | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>\ </kbd> | | Go to bracket | -| <kbd>Control</kbd> + <kbd>G</kbd> | | Go to line or column | -| <kbd>Option</kbd> + <kbd>F8</kbd> | | Go to next problem (error, warning, information) | -| <kbd>F8</kbd> | | Go to next problem in files (error, warning, information) | -| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>F8</kbd> | | Go to previous problem (error, warning, information) | -| <kbd>Shift</kbd> + <kbd>F8</kbd> | | Go to previous problem in files (error, warning, information) | -| <kbd>Command</kbd> + <kbd>]</kbd> | | Indent line | -| <kbd>Shift</kbd> + <kbd>Command</kbd> | | Enter Insert line above | -| <kbd>Command</kbd> + <kbd>Enter</kbd> | | Insert line below | -| <kbd>Control</kbd> + <kbd>J</kbd> | | Join lines | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>D</kbd> | | Move last selection to next find match | -| <kbd>Option</kbd> + <kbd>↓</kbd> | | Move line down | -| <kbd>Option</kbd> + <kbd>↑</kbd> | | Move line up | -| <kbd>Command</kbd> + <kbd>[</kbd> | | Outdent line | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>P</kbd> | | Preview Markdown | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>U</kbd> | | Remove line comment | -| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>F</kbd> | | Replace | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>.</kbd> | | Replace with next value | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>,</kbd> | | Replace with previous value | -| <kbd>Command</kbd> + <kbd>S</kbd> | | Save files | -| <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>P</kbd> | Search for, and then open another file for editing. | -| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>L</kbd> | | Select all occurrence of Find Match | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>B</kbd> | | Set selection anchor | -| <kbd>Option</kbd> + <kbd>F1</kbd> | | Show accessibility help | -| <kbd>Shift</kbd> + <kbd>F10</kbd> | | Show editor context menu | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>I</kbd> | | Show hover | -| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>←</kbd> | | Shrink selection | -| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>A</kbd> | | Toggle block comment | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>L</kbd> | | Toggle fold | -| <kbd>Command</kbd> + <kbd>/</kbd> | | Toggle line comment | -| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | | Toggle Tab key moves focus | +| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>[</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>[</kbd> | Fold | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>O</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>O</kbd> | Fold all | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>/</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>/</kbd> | Fold all block comments | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>8</kbd> | Fold all regions | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>-</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>-</kbd> | Fold all regions except selected | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>1</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>1</kbd> | Fold level 1 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>2</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>2</kbd> | Fold level 2 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>3</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>3</kbd> | Fold level 3 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>4</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>4</kbd> | Fold level 4 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>5</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>5</kbd> | Fold level 5 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>6</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>6</kbd> | Fold level 6 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>7</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>7</kbd> | Fold level 7 | +| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>[</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>[</kbd> | Fold recursively | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>\</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>\</kbd> | Go to bracket | +| <kbd>Control</kbd> + <kbd>G</kbd> | <kbd>Control</kbd> + <kbd>G</kbd> | Go to line or column | +| <kbd>Option</kbd> + <kbd>F8</kbd> | <kbd>Alt</kbd> + <kbd>F8</kbd> | Go to next problem (error, warning, information) | +| <kbd>F8</kbd> | <kbd>F8</kbd> | Go to next problem in files (error, warning, information) | +| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>F8</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>F8</kbd> | Go to previous problem (error, warning, information) | +| <kbd>Shift</kbd> + <kbd>F8</kbd> | <kbd>Shift</kbd> + <kbd>F8</kbd> | Go to previous problem in files (error, warning, information) | +| <kbd>Command</kbd> + <kbd>]</kbd> | <kbd>Control</kbd> + <kbd>]</kbd> | Indent line | +| <kbd>Shift</kbd> + <kbd>Command</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Enter</kbd> | Insert line above | +| <kbd>Command</kbd> + <kbd>Enter</kbd> | <kbd>Control</kbd> + <kbd>Enter</kbd> | Insert line below | +| <kbd>Control</kbd> + <kbd>J</kbd> | <kbd>Control</kbd> + <kbd>J</kbd> | Join lines [(Linux note)](#linux-shortcuts) | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>D</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>D</kbd> | Move last selection to next find match | +| <kbd>Option</kbd> + <kbd>↓</kbd> | <kbd>Alt</kbd> + <kbd>↓</kbd> | Move line down | +| <kbd>Option</kbd> + <kbd>↑</kbd> | <kbd>Alt</kbd> + <kbd>↑</kbd> | Move line up | +| <kbd>Command</kbd> + <kbd>[</kbd> | <kbd>Control</kbd> + <kbd>[</kbd> | Outdent line | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> | Preview Markdown [(Linux note)](#linux-shortcuts) | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>U</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>U</kbd> or <kbd>Control</kbd> + <kbd>/</kbd> | Remove line comment | +| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>F</kbd> | <kbd>Control</kbd> + <kbd>F</kbd> | Replace | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>.</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>.</kbd> | Replace with next value | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>,</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>,</kbd> | Replace with previous value | +| <kbd>Command</kbd> + <kbd>S</kbd> | <kbd>Control</kbd> + <kbd>S</kbd> | Save files | +| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>L</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Select all occurrences of find match | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>B</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>B</kbd> | Set selection anchor | +| <kbd>Option</kbd> + <kbd>F1</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>F1</kbd> | Show accessibility help | +| <kbd>Shift</kbd> + <kbd>F10</kbd> | <kbd>Shift</kbd> + <kbd>F10</kbd> | Show editor context menu | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>I</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>I</kbd> | Show hover | +| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>←</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>←</kbd> | Shrink selection | +| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>A</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> | Toggle block comment | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>L</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>L</kbd> | Toggle fold | +| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | <kbd>Control</kbd> + <kbd>M</kbd> | Toggle Tab key moves focus | +| <kbd>Command</kbd> + <kbd>/</kbd> | <kbd>Control</kbd> + <kbd>/</kbd> | Toggle line comment | | <kbd>Control</kbd> + <kbd>T</kbd> | | Transpose letters | -| <kbd>Control</kbd> + <kbd>Space</kbd> | | Trigger Suggest | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | | Trim trailing whitespace | -| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>]</kbd> | | Unfold | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>J</kbd> | | Unfold all | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>9</kbd> | | Unfold all regions | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>=</kbd> | | Unfold all regions except selected | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>]</kbd> | | Unfold recursively | -| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | | Trim trailing whitespace | +| <kbd>Control</kbd> + <kbd>Space</kbd> | <kbd>Control</kbd> + <kbd>Space</kbd> | Trigger Suggest | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>X</kbd> | Trim trailing whitespace | +| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>]</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>]</kbd> | Unfold | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>J</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>J</kbd> | Unfold all | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>9</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>9</kbd> | Unfold all regions | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>=</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>=</kbd> | Unfold all regions except selected | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>]</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>]</kbd> | Unfold recursively | +| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>X</kbd> | Trim trailing whitespace | | <kbd>Command</kbd> + <kbd>Enter</kbd> | <kbd>Control</kbd> + <kbd>Enter</kbd> | Commit (when editing the commit message). | ### Repository graph @@ -267,7 +267,7 @@ These shortcuts are available when editing a file with the | <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>5</kbd> | <kbd>Control</kbd> + <kbd>Alt</kbd> + <kbd>5</kbd> | Apply heading style 5 | | <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>6</kbd> | <kbd>Control</kbd> + <kbd>Alt</kbd> + <kbd>6</kbd> | Apply heading style 6 | | <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>7</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>7</kbd> | Ordered list | -| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | Bullet list | +| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | Unordered list | | <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>9</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>9</kbd> | Task list | | <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>b</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>b</kbd> | Blockquote | | <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>c</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>c</kbd> | Code block | @@ -328,3 +328,10 @@ To disable keyboard shortcuts: 1. While viewing a page that supports keyboard shortcuts, and outside a text box, press <kbd>?</kbd> to display the list of shortcuts. 1. Select **Toggle shortcuts**. + +## Troubleshooting + +### Linux shortcuts + +Linux users may encounter GitLab keyboard shortcuts that are overridden by +their operating system, or their browser. diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb index d94bf060d05..a90cae7aea2 100644 --- a/lib/gitlab/database/background_migration/batched_migration.rb +++ b/lib/gitlab/database/background_migration/batched_migration.rb @@ -28,6 +28,8 @@ module Gitlab # on_hold_until is a temporary runtime status which puts execution "on hold" scope :executable, -> { with_status(:active).where('on_hold_until IS NULL OR on_hold_until < NOW()') } + scope :created_after, ->(time) { where('created_at > ?', time) } + scope :for_configuration, ->(job_class_name, table_name, column_name, job_arguments) do where(job_class_name: job_class_name, table_name: table_name, column_name: column_name) .where("job_arguments = ?", job_arguments.to_json) # rubocop:disable Rails/WhereEquals diff --git a/lib/gitlab/database/migrations/base_background_runner.rb b/lib/gitlab/database/migrations/base_background_runner.rb new file mode 100644 index 00000000000..2772502140e --- /dev/null +++ b/lib/gitlab/database/migrations/base_background_runner.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module Migrations + class BaseBackgroundRunner + attr_reader :result_dir + + def initialize(result_dir:) + @result_dir = result_dir + end + + def jobs_by_migration_name + raise NotImplementedError, 'subclass must implement' + end + + def run_job(job) + raise NotImplementedError, 'subclass must implement' + end + + def run_jobs(for_duration:) + jobs_to_run = jobs_by_migration_name + return if jobs_to_run.empty? + + # without .to_f, we do integer division + # For example, 3.minutes / 2 == 1.minute whereas 3.minutes / 2.to_f == (1.minute + 30.seconds) + duration_per_migration_type = for_duration / jobs_to_run.count.to_f + jobs_to_run.each do |migration_name, jobs| + run_until = duration_per_migration_type.from_now + + run_jobs_for_migration(migration_name: migration_name, jobs: jobs, run_until: run_until) + end + end + + private + + def run_jobs_for_migration(migration_name:, jobs:, run_until:) + per_background_migration_result_dir = File.join(@result_dir, migration_name) + + instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir) + batch_names = (1..).each.lazy.map { |i| "batch_#{i}"} + + jobs.shuffle.each do |j| + break if run_until <= Time.current + + instrumentation.observe(version: nil, + name: batch_names.next, + connection: ActiveRecord::Migration.connection) do + run_job(j) + end + end + end + end + end + end +end diff --git a/lib/gitlab/database/migrations/runner.rb b/lib/gitlab/database/migrations/runner.rb index 3b6f52b43a8..4404b5bf961 100644 --- a/lib/gitlab/database/migrations/runner.rb +++ b/lib/gitlab/database/migrations/runner.rb @@ -21,6 +21,18 @@ module Gitlab TestBackgroundRunner.new(result_dir: BASE_RESULT_DIR.join('background_migrations')) end + def batched_background_migrations(for_database:) + runner = nil + + # Only one loop iteration since we pass `only:` here + Gitlab::Database::EachDatabase.each_database_connection(only: for_database) do |connection| + runner = Gitlab::Database::Migrations::TestBatchedBackgroundRunner + .new(result_dir: BASE_RESULT_DIR.join('background_migrations'), connection: connection) + end + + runner + end + def migration_context @migration_context ||= ApplicationRecord.connection.migration_context end diff --git a/lib/gitlab/database/migrations/test_background_runner.rb b/lib/gitlab/database/migrations/test_background_runner.rb index 74e54d62e05..f7713237b38 100644 --- a/lib/gitlab/database/migrations/test_background_runner.rb +++ b/lib/gitlab/database/migrations/test_background_runner.rb @@ -3,11 +3,9 @@ module Gitlab module Database module Migrations - class TestBackgroundRunner - attr_reader :result_dir - + class TestBackgroundRunner < BaseBackgroundRunner def initialize(result_dir:) - @result_dir = result_dir + super(result_dir: result_dir) @job_coordinator = Gitlab::BackgroundMigration.coordinator_for_database(Gitlab::Database::MAIN_DATABASE_NAME) end @@ -15,37 +13,12 @@ module Gitlab @job_coordinator.pending_jobs end - def run_jobs(for_duration:) - jobs_to_run = traditional_background_migrations.group_by { |j| class_name_for_job(j) } - return if jobs_to_run.empty? - - # without .to_f, we do integer division - # For example, 3.minutes / 2 == 1.minute whereas 3.minutes / 2.to_f == (1.minute + 30.seconds) - duration_per_migration_type = for_duration / jobs_to_run.count.to_f - jobs_to_run.each do |migration_name, jobs| - run_until = duration_per_migration_type.from_now - - run_jobs_for_migration(migration_name: migration_name, jobs: jobs, run_until: run_until) - end + def jobs_by_migration_name + traditional_background_migrations.group_by { |j| class_name_for_job(j) } end private - def run_jobs_for_migration(migration_name:, jobs:, run_until:) - per_background_migration_result_dir = File.join(@result_dir, migration_name) - - instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir) - batch_names = (1..).each.lazy.map { |i| "batch_#{i}"} - - jobs.shuffle.each do |j| - break if run_until <= Time.current - - instrumentation.observe(version: nil, name: batch_names.next, connection: ActiveRecord::Migration.connection) do - run_job(j) - end - end - end - def run_job(job) Gitlab::BackgroundMigration.perform(job.args[0], job.args[1]) end diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb new file mode 100644 index 00000000000..0c6a8d3d856 --- /dev/null +++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module Migrations + class TestBatchedBackgroundRunner < BaseBackgroundRunner + attr_reader :connection + + def initialize(result_dir:, connection:) + super(result_dir: result_dir) + @connection = connection + end + + def jobs_by_migration_name + Gitlab::Database::BackgroundMigration::BatchedMigration + .executable + .created_after(2.days.ago) # Simple way to exclude migrations already running before migration testing + .to_h do |migration| + batching_strategy = migration.batch_class.new(connection: connection) + + all_migration_jobs = [] + + min_value = migration.next_min_value + + while (next_bounds = batching_strategy.next_batch( + migration.table_name, + migration.column_name, + batch_min_value: min_value, + batch_size: migration.batch_size, + job_arguments: migration.job_arguments + )) + + batch_min, batch_max = next_bounds + + all_migration_jobs << migration.create_batched_job!(batch_min, batch_max) + min_value = batch_max + 1 + end + + [migration.job_class_name, all_migration_jobs] + end + end + + def run_job(job) + Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper.new(connection: connection).perform(job) + end + end + end + end +end diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb index 835fc94e5d8..5496bd5f682 100644 --- a/lib/gitlab/template/gitlab_ci_yml_template.rb +++ b/lib/gitlab/template/gitlab_ci_yml_template.rb @@ -6,8 +6,6 @@ module Gitlab BASE_EXCLUDED_PATTERNS = [%r{\.latest\.}].freeze BASE_DIR = 'lib/gitlab/ci/templates' - TEMPLATES_WITH_LATEST_VERSION = {}.freeze - def description "# This file is a template, and might need editing before it works on your project." end @@ -58,31 +56,6 @@ module Gitlab excluded_patterns: self.excluded_patterns ) end - - override :find - def find(key, project = nil) - if try_redirect_to_latest?(key, project) - key += '.latest' - end - - super(key, project) - end - - private - - # To gauge the impact of the latest template, - # you can redirect the stable template to the latest template by enabling the feature flag. - # See https://docs.gitlab.com/ee/development/cicd/templates.html#versioning for more information. - def try_redirect_to_latest?(key, project) - return false unless templates_with_latest_version[key] - - flag_name = "redirect_to_latest_template_#{key.underscore.tr('/', '_')}" - ::Feature.enabled?(flag_name, project) - end - - def templates_with_latest_version - TEMPLATES_WITH_LATEST_VERSION - end end end end diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index ff1587a3719..068dc463d16 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -328,6 +328,15 @@ namespace :gitlab do Gitlab::Database::Migrations::Runner.background_migrations.run_jobs(for_duration: duration) end + + desc 'Sample batched background migrations with instrumentation' + task :sample_batched_background_migrations, [:database, :duration_s] => [:environment] do |_t, args| + database_name = args[:database] || 'main' + duration = args[:duration_s]&.to_i&.seconds || 30.minutes # Default of 30 minutes + + Gitlab::Database::Migrations::Runner.batched_background_migrations(for_database: database_name) + .run_jobs(for_duration: duration) + end end desc 'Run all pending batched migrations' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6f83d3c0caa..e0a4e9e7c61 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -454,6 +454,9 @@ msgstr "" msgid "%{authorsName}'s thread" msgstr "" +msgid "%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}" +msgstr "" + msgid "%{board_target} not found" msgstr "" diff --git a/qa/qa/page/group/settings/group_deploy_tokens.rb b/qa/qa/page/group/settings/group_deploy_tokens.rb index 65ee3fc72eb..7d908f473de 100644 --- a/qa/qa/page/group/settings/group_deploy_tokens.rb +++ b/qa/qa/page/group/settings/group_deploy_tokens.rb @@ -30,10 +30,10 @@ module QA end def fill_scopes(read_repository: false, read_registry: false, read_package_registry: false, write_package_registry: false ) - check_element(:deploy_token_read_repository_checkbox) if read_repository - check_element(:deploy_token_read_package_registry_checkbox) if read_package_registry - check_element(:deploy_token_read_registry_checkbox) if read_registry - check_element(:deploy_token_write_package_registry_checkbox) if write_package_registry + check_element(:deploy_token_read_repository_checkbox, true) if read_repository + check_element(:deploy_token_read_package_registry_checkbox, true) if read_package_registry + check_element(:deploy_token_read_registry_checkbox, true) if read_registry + check_element(:deploy_token_write_package_registry_checkbox, true) if write_package_registry end def add_token diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index d8b7bb90437..30d1895781a 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -30,9 +30,8 @@ module QA element :register_tab end - view 'app/views/devise/shared/_tabs_normal.html.haml' do + view 'app/views/devise/shared/_tab_single.html.haml' do element :sign_in_tab - element :register_tab end view 'app/helpers/auth_helper.rb' do diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb index 407d57bc54e..cf25f4a0568 100644 --- a/qa/qa/page/project/settings/deploy_tokens.rb +++ b/qa/qa/page/project/settings/deploy_tokens.rb @@ -31,11 +31,25 @@ module QA end def fill_scopes(scopes) - check_element(:deploy_token_read_repository_checkbox) if scopes.include? :read_repository - check_element(:deploy_token_read_package_registry_checkbox) if scopes.include? :read_package_registry - check_element(:deploy_token_write_package_registry_checkbox) if scopes.include? :write_package_registry - check_element(:deploy_token_read_registry_checkbox) if scopes.include? :read_registry - check_element(:deploy_token_write_registry_checkbox) if scopes.include? :write_registry + if scopes.include? :read_repository + check_element(:deploy_token_read_repository_checkbox, true) + end + + if scopes.include? :read_package_registry + check_element(:deploy_token_read_package_registry_checkbox, true) + end + + if scopes.include? :write_package_registry + check_element(:deploy_token_write_package_registry_checkbox, true) + end + + if scopes.include? :read_registry + check_element(:deploy_token_read_registry_checkbox, true) + end + + if scopes.include? :write_registry + check_element(:deploy_token_write_registry_checkbox, true) + end end def add_token diff --git a/scripts/used-feature-flags b/scripts/used-feature-flags index 89ea99c6984..0966795f451 100755 --- a/scripts/used-feature-flags +++ b/scripts/used-feature-flags @@ -70,13 +70,6 @@ flags_paths.each do |flags_path| next end - # Dynamic feature flag names for redirect to latest CI templates - # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63144/diffs#fa2193ace3f6a02f7ef9995ef9bc519eca92c4ee_57_84 - if feature_flag_name.start_with?('redirect_to_latest_template_') - puts "Skipping the #{feature_flag_name} feature flag since it starts with 'redirect_to_latest_template_'." - next - end - all_flags[feature_flag_name] = File.exist?(File.join('tmp', 'feature_flags', feature_flag_name + '.used')) end end diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb index 33443509e4a..56ad0943377 100644 --- a/spec/config/object_store_settings_spec.rb +++ b/spec/config/object_store_settings_spec.rb @@ -162,14 +162,14 @@ RSpec.describe ObjectStoreSettings do { 'enabled' => true, 'remote_directory' => 'some-bucket', - 'direct_upload' => true, - 'background_upload' => false, + 'direct_upload' => false, + 'background_upload' => true, 'proxy_download' => false } end before do - settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings) + settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings, 'lfs') end it 'does not alter config if legacy settings are specified' do @@ -177,6 +177,35 @@ RSpec.describe ObjectStoreSettings do expect(settings.artifacts['object_store']).to be_nil expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket') + # Disable background_upload, regardless of the input config + expect(settings.lfs['object_store']['direct_upload']).to eq(true) + expect(settings.lfs['object_store']['background_upload']).to eq(false) + expect(settings.external_diffs['object_store']).to be_nil + end + end + + context 'with legacy config and legacy background upload is enabled' do + let(:legacy_settings) do + { + 'enabled' => true, + 'remote_directory' => 'some-bucket', + 'proxy_download' => false + } + end + + before do + stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'lfs') + settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings, 'lfs') + end + + it 'enables background_upload and disables direct_upload' do + subject + + expect(settings.artifacts['object_store']).to be_nil + expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket') + # Enable background_upload if the environment variable is available + expect(settings.lfs['object_store']['direct_upload']).to eq(false) + expect(settings.lfs['object_store']['background_upload']).to eq(true) expect(settings.external_diffs['object_store']).to be_nil end end @@ -185,11 +214,11 @@ RSpec.describe ObjectStoreSettings do describe '.legacy_parse' do it 'sets correct default values' do - settings = described_class.legacy_parse(nil) + settings = described_class.legacy_parse(nil, 'artifacts') expect(settings['enabled']).to be false - expect(settings['direct_upload']).to be false - expect(settings['background_upload']).to be true + expect(settings['direct_upload']).to be true + expect(settings['background_upload']).to be false expect(settings['remote_directory']).to be nil end @@ -199,12 +228,52 @@ RSpec.describe ObjectStoreSettings do 'remote_directory' => 'artifacts' }) - settings = described_class.legacy_parse(original_settings) + settings = described_class.legacy_parse(original_settings, 'artifacts') expect(settings['enabled']).to be true - expect(settings['direct_upload']).to be false - expect(settings['background_upload']).to be true + expect(settings['direct_upload']).to be true + expect(settings['background_upload']).to be false expect(settings['remote_directory']).to eq 'artifacts' end + + context 'legacy background upload environment variable is enabled' do + before do + stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'artifacts,lfs') + end + + it 'enables background_upload and disables direct_upload' do + original_settings = Settingslogic.new({ + 'enabled' => true, + 'remote_directory' => 'artifacts' + }) + + settings = described_class.legacy_parse(original_settings, 'artifacts') + + expect(settings['enabled']).to be true + expect(settings['direct_upload']).to be false + expect(settings['background_upload']).to be true + expect(settings['remote_directory']).to eq 'artifacts' + end + end + + context 'legacy background upload environment variable is enabled for other types' do + before do + stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'uploads,lfs') + end + + it 'enables direct_upload and disables background_upload' do + original_settings = Settingslogic.new({ + 'enabled' => true, + 'remote_directory' => 'artifacts' + }) + + settings = described_class.legacy_parse(original_settings, 'artifacts') + + expect(settings['enabled']).to be true + expect(settings['direct_upload']).to be true + expect(settings['background_upload']).to be false + expect(settings['remote_directory']).to eq 'artifacts' + end + end end end diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb index a680ec78b2f..159306b28d8 100644 --- a/spec/features/merge_request/user_accepts_merge_request_spec.rb +++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb @@ -112,9 +112,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli click_button('Merge') - page.within('.status-box') do - expect(page).to have_content('Merged') - end + expect(page).to have_selector('.gl-badge', text: 'Merged') end end end diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb index 21b6352e450..a3dc3079374 100644 --- a/spec/features/merge_request/user_creates_merge_request_spec.rb +++ b/spec/features/merge_request/user_creates_merge_request_spec.rb @@ -118,7 +118,7 @@ RSpec.describe "User creates a merge request", :js do click_button("Create merge request") - expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.path}:#{source_branch} into master") + expect(page).to have_content(title).and have_content("requested to merge #{forked_project.full_path}:#{source_branch} into master") end end end diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb index 12518d634ec..6a9a30953df 100644 --- a/spec/features/merge_request/user_merges_merge_request_spec.rb +++ b/spec/features/merge_request/user_merges_merge_request_spec.rb @@ -17,9 +17,7 @@ RSpec.describe "User merges a merge request", :js do click_button("Merge") end - page.within(".status-box") do - expect(page).to have_content("Merged") - end + expect(page).to have_selector('.gl-badge', text: 'Merged') end end diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index a145bcb976b..fbb4847130c 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -71,13 +71,14 @@ RSpec.describe 'User views an open merge request' do let(:merge_request) { create(:merge_request, :rebased, source_project: project, target_project: project) } before do + project.add_maintainer(project.creator) + sign_in(project.creator) + visit(merge_request_path(merge_request)) end it 'does not show diverged commits count' do - page.within('.mr-source-target') do - expect(page).not_to have_content(/([0-9]+ commits? behind)/) - end + expect(page).not_to have_content(/([0-9]+ commits? behind)/) end end @@ -85,13 +86,14 @@ RSpec.describe 'User views an open merge request' do let(:merge_request) { create(:merge_request, :diverged, source_project: project, target_project: project) } before do + project.add_maintainer(project.creator) + sign_in(project.creator) + visit(merge_request_path(merge_request)) end it 'shows diverged commits count' do - page.within('.mr-source-target') do - expect(page).to have_content(/([0-9]+ commits behind)/) - end + expect(page).not_to have_content(/([0-9]+ commits? behind)/) end end diff --git a/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb b/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb index a6de443e96f..f637186ec67 100644 --- a/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb @@ -11,6 +11,10 @@ RSpec.describe 'Project > Merge request > View user status' do subject { visit merge_request_path(merge_request) } describe 'the status of the merge request author' do + before do + stub_feature_flags(updated_mr_header: false) + end + it_behaves_like 'showing user status' do let(:user_with_status) { merge_request.author } end diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js index e1811247124..bd5f74247f4 100644 --- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -291,7 +291,7 @@ describe('ShortcutsIssuable', () => { window.shortcut = new ShortcutsIssuable(); [sidebarCollapsedBtn, sidebarExpandedBtn] = document.querySelectorAll( - '.js-sidebar-source-branch button', + '.js-source-branch-copy', ); [sidebarCollapsedBtn, sidebarExpandedBtn].forEach((btn) => jest.spyOn(btn, 'click')); @@ -312,22 +312,6 @@ describe('ShortcutsIssuable', () => { it('clicks the "expanded" version of the copy source branch button', () => { expect(sidebarExpandedBtn.click).toHaveBeenCalled(); - expect(sidebarCollapsedBtn.click).not.toHaveBeenCalled(); - }); - }); - - describe('when the sidebar is collapsed', () => { - beforeEach(() => { - // simulate the applied CSS styles when the - // sidebar is collapsed - sidebarExpandedBtn.style.display = 'none'; - - Mousetrap.trigger('b'); - }); - - it('clicks the "collapsed" version of the copy source branch button', () => { - expect(sidebarCollapsedBtn.click).toHaveBeenCalled(); - expect(sidebarExpandedBtn.click).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/issuable/components/status_box_spec.js b/spec/frontend/issuable/components/status_box_spec.js index 9cbf023dbd6..d3e05939527 100644 --- a/spec/frontend/issuable/components/status_box_spec.js +++ b/spec/frontend/issuable/components/status_box_spec.js @@ -1,37 +1,37 @@ -import { GlIcon, GlSprintf } from '@gitlab/ui'; +import { GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import StatusBox from '~/issuable/components/status_box.vue'; let wrapper; function factory(propsData) { - wrapper = shallowMount(StatusBox, { propsData, stubs: { GlSprintf } }); + wrapper = shallowMount(StatusBox, { + propsData, + stubs: { GlSprintf }, + provide: { glFeatures: { updatedMrHeader: true } }, + }); } const testCases = [ { name: 'Open', state: 'opened', - class: 'status-box-open', - icon: 'issue-open-m', + class: 'badge-success', }, { name: 'Open', state: 'locked', - class: 'status-box-open', - icon: 'issue-open-m', + class: 'badge-success', }, { name: 'Closed', state: 'closed', - class: 'status-box-mr-closed', - icon: 'issue-close', + class: 'badge-danger', }, { name: 'Merged', state: 'merged', - class: 'status-box-mr-merged', - icon: 'git-merge', + class: 'badge-info', }, ]; @@ -46,6 +46,7 @@ describe('Merge request status box component', () => { it('renders human readable test', () => { factory({ initialState: testCase.state, + issuableType: 'merge_request', }); expect(wrapper.text()).toContain(testCase.name); @@ -54,18 +55,11 @@ describe('Merge request status box component', () => { it('sets css class', () => { factory({ initialState: testCase.state, + issuableType: 'merge_request', }); expect(wrapper.classes()).toContain(testCase.class); }); - - it('renders icon', () => { - factory({ - initialState: testCase.state, - }); - - expect(wrapper.findComponent(GlIcon).props('name')).toBe(testCase.icon); - }); }); }); }); diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index 7a433be0e2f..a1c979bba50 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -99,6 +99,15 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m end end + describe '.created_after' do + let!(:migration_old) { create :batched_background_migration, created_at: 2.days.ago } + let!(:migration_new) { create :batched_background_migration, created_at: 0.days.ago } + + it 'only returns migrations created after the specified time' do + expect(described_class.created_after(1.day.ago)).to contain_exactly(migration_new) + end + end + describe '.queued' do let!(:migration1) { create(:batched_background_migration, :finished) } let!(:migration2) { create(:batched_background_migration, :paused) } diff --git a/spec/lib/gitlab/database/migrations/base_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/base_background_runner_spec.rb new file mode 100644 index 00000000000..34c83c42056 --- /dev/null +++ b/spec/lib/gitlab/database/migrations/base_background_runner_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::Migrations::BaseBackgroundRunner, :freeze_time do + let(:result_dir) { Dir.mktmpdir } + + after do + FileUtils.rm_rf(result_dir) + end + + context 'subclassing' do + subject { described_class.new(result_dir: result_dir) } + + it 'requires that jobs_by_migration_name be implemented' do + expect { subject.jobs_by_migration_name }.to raise_error(NotImplementedError) + end + + it 'requires that run_job be implemented' do + expect { subject.run_job(nil) }.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/lib/gitlab/database/migrations/runner_spec.rb b/spec/lib/gitlab/database/migrations/runner_spec.rb index 8b1ccf05eb1..e7f68e3e4a8 100644 --- a/spec/lib/gitlab/database/migrations/runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/runner_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Migrations::Runner do + include Database::MultipleDatabases + let(:result_dir) { Pathname.new(Dir.mktmpdir) } let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations @@ -136,4 +138,35 @@ RSpec.describe Gitlab::Database::Migrations::Runner do expect(runner.result_dir).to eq(described_class::BASE_RESULT_DIR.join( 'background_migrations')) end end + + describe '.batched_background_migrations' do + it 'is a TestBatchedBackgroundRunner' do + expect(described_class.batched_background_migrations(for_database: 'main')).to be_a(Gitlab::Database::Migrations::TestBatchedBackgroundRunner) + end + + context 'choosing the database to test against' do + it 'chooses the main database' do + runner = described_class.batched_background_migrations(for_database: 'main') + + chosen_connection_name = Gitlab::Database.db_config_name(runner.connection) + + expect(chosen_connection_name).to eq('main') + end + + it 'chooses the ci database' do + skip_if_multiple_databases_not_setup + + runner = described_class.batched_background_migrations(for_database: 'ci') + + chosen_connection_name = Gitlab::Database.db_config_name(runner.connection) + + expect(chosen_connection_name).to eq('ci') + end + + it 'throws an error with an invalid name' do + expect { described_class.batched_background_migrations(for_database: 'not_a_database') } + .to raise_error(/not a valid database name/) + end + end + end end diff --git a/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb index 9407efad91f..eb7f37e02ef 100644 --- a/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do include Gitlab::Database::Migrations::BackgroundMigrationHelpers + include Database::MigrationTestingHelpers # In order to test the interaction between queueing sidekiq jobs and seeing those jobs in queues, # we need to disable sidekiq's testing mode and actually send our jobs to redis @@ -41,40 +42,6 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do end context 'running migrations', :freeze_time do - def define_background_migration(name) - klass = Class.new do - # Can't simply def perform here as we won't have access to the block, - # similarly can't define_method(:perform, &block) here as it would change the block receiver - define_method(:perform) { |*args| yield(*args) } - end - stub_const("Gitlab::BackgroundMigration::#{name}", klass) - klass - end - - def expect_migration_call_counts(migrations_to_calls) - migrations_to_calls.each do |migration, calls| - expect_next_instances_of(migration, calls) do |m| - expect(m).to receive(:perform).and_call_original - end - end - end - - def expect_recorded_migration_runs(migrations_to_runs) - migrations_to_runs.each do |migration, runs| - path = File.join(result_dir, migration.name.demodulize) - num_subdirs = Pathname(path).children.count(&:directory?) - expect(num_subdirs).to eq(runs) - end - end - - def expect_migration_runs(migrations_to_run_counts) - expect_migration_call_counts(migrations_to_run_counts) - - yield - - expect_recorded_migration_runs(migrations_to_run_counts) - end - it 'runs the migration class correctly' do calls = [] define_background_migration(migration_name) do |i| diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb new file mode 100644 index 00000000000..fbfff1268cc --- /dev/null +++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freeze_time do + include Gitlab::Database::MigrationHelpers + include Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers + include Database::MigrationTestingHelpers + + let(:result_dir) { Dir.mktmpdir } + + after do + FileUtils.rm_rf(result_dir) + end + + let(:connection) { ApplicationRecord.connection } + + let(:table_name) { "_test_column_copying"} + + before do + connection.execute(<<~SQL) + CREATE TABLE #{table_name} ( + id bigint primary key not null, + data bigint + ); + + insert into #{table_name} (id) select i from generate_series(1, 1000) g(i); + SQL + end + + context 'running a real background migration' do + it 'runs sampled jobs from the batched background migration' do + queue_batched_background_migration('CopyColumnUsingBackgroundMigrationJob', + table_name, :id, + :id, :data, + batch_size: 100, + job_interval: 5.minutes) # job_interval is skipped when testing + described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 1.minute) + unmigrated_row_count = define_batchable_model(table_name).where('id != data').count + + expect(unmigrated_row_count).to eq(0) + end + end + + context 'with jobs to run' do + let(:migration_name) { 'TestBackgroundMigration' } + + before do + queue_batched_background_migration(migration_name, table_name, :id, job_interval: 5.minutes, batch_size: 100) + end + + it 'samples jobs' do + calls = [] + define_background_migration(migration_name) do |*args| + calls << args + end + + described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 3.minutes) + + expect(calls.count).to eq(10) # 1000 rows / batch size 100 = 10 + end + + context 'with multiple jobs to run' do + it 'runs all jobs created within the last 48 hours' do + old_migration = define_background_migration(migration_name) + + travel 3.days + + new_migration = define_background_migration('NewMigration') { travel 1.second } + queue_batched_background_migration('NewMigration', table_name, :id, + job_interval: 5.minutes, + batch_size: 10, + sub_batch_size: 5) + + other_new_migration = define_background_migration('NewMigration2') { travel 2.seconds } + queue_batched_background_migration('NewMigration2', table_name, :id, + job_interval: 5.minutes, + batch_size: 10, + sub_batch_size: 5) + + expect_migration_runs(new_migration => 3, other_new_migration => 2, old_migration => 0) do + described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 5.seconds) + end + end + end + end +end diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb index 226fdb9c948..26c83ed6793 100644 --- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb +++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb @@ -21,55 +21,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do end end - describe '.find' do - let_it_be(:project) { create(:project) } - let_it_be(:other_project) { create(:project) } - - described_class::TEMPLATES_WITH_LATEST_VERSION.keys.each do |key| - it "finds the latest template for #{key}" do - result = described_class.find(key, project) - expect(result.full_name).to eq("#{key}.latest.gitlab-ci.yml") - expect(result.content).to be_present - end - - context 'when `redirect_to_latest_template` feature flag is disabled' do - before do - stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => false) - end - - it "finds the stable template for #{key}" do - result = described_class.find(key, project) - expect(result.full_name).to eq("#{key}.gitlab-ci.yml") - expect(result.content).to be_present - end - end - - context 'when `redirect_to_latest_template` feature flag is enabled on the project' do - before do - stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => project) - end - - it "finds the latest template for #{key}" do - result = described_class.find(key, project) - expect(result.full_name).to eq("#{key}.latest.gitlab-ci.yml") - expect(result.content).to be_present - end - end - - context 'when `redirect_to_latest_template` feature flag is enabled on the other project' do - before do - stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => other_project) - end - - it "finds the stable template for #{key}" do - result = described_class.find(key, project) - expect(result.full_name).to eq("#{key}.gitlab-ci.yml") - expect(result.content).to be_present - end - end - end - end - describe '#content' do it 'loads the full file' do gitignore = subject.new(Rails.root.join('lib/gitlab/ci/templates/Ruby.gitlab-ci.yml')) diff --git a/spec/support/helpers/database/migration_testing_helpers.rb b/spec/support/helpers/database/migration_testing_helpers.rb new file mode 100644 index 00000000000..916446e66b7 --- /dev/null +++ b/spec/support/helpers/database/migration_testing_helpers.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Database + module MigrationTestingHelpers + def define_background_migration(name) + klass = Class.new do + # Can't simply def perform here as we won't have access to the block, + # similarly can't define_method(:perform, &block) here as it would change the block receiver + define_method(:perform) { |*args| yield(*args) } + end + stub_const("Gitlab::BackgroundMigration::#{name}", klass) + klass + end + + def expect_migration_call_counts(migrations_to_calls) + migrations_to_calls.each do |migration, calls| + expect_next_instances_of(migration, calls) do |m| + expect(m).to receive(:perform).and_call_original + end + end + end + + def expect_recorded_migration_runs(migrations_to_runs) + migrations_to_runs.each do |migration, runs| + path = File.join(result_dir, migration.name.demodulize) + if runs.zero? + expect(Pathname(path)).not_to be_exist + else + num_subdirs = Pathname(path).children.count(&:directory?) + expect(num_subdirs).to eq(runs) + end + end + end + + def expect_migration_runs(migrations_to_run_counts) + expect_migration_call_counts(migrations_to_run_counts) + + yield + + expect_recorded_migration_runs(migrations_to_run_counts) + end + end +end diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index 73f3b55e12e..e340d568269 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -697,6 +697,34 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do run_rake_task('gitlab:db:migration_testing:sample_background_migrations', '[100]') end end + + describe '#sample_batched_background_migrations' do + let(:batched_runner) { instance_double(::Gitlab::Database::Migrations::TestBatchedBackgroundRunner) } + + it 'delegates to the migration runner for the main database with a default sample duration' do + expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations) + .with(for_database: 'main').and_return(batched_runner) + expect(batched_runner).to receive(:run_jobs).with(for_duration: 30.minutes) + + run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations') + end + + it 'delegates to the migration runner for a specified database with a default sample duration' do + expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations) + .with(for_database: 'ci').and_return(batched_runner) + expect(batched_runner).to receive(:run_jobs).with(for_duration: 30.minutes) + + run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations', '[ci]') + end + + it 'delegates to the migration runner for a specified database and sample duration' do + expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations) + .with(for_database: 'ci').and_return(batched_runner) + expect(batched_runner).to receive(:run_jobs).with(for_duration: 100.seconds) + + run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations', '[ci, 100]') + end + end end describe '#execute_batched_migrations' do |