Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b031a57ae7
commit
b558e1ad8f
76 changed files with 875 additions and 445 deletions
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,6 +36,7 @@ export default function initMergeRequestShow() {
|
|||
return h(StatusBox, {
|
||||
props: {
|
||||
initialState: el.dataset.state,
|
||||
issuableType: 'merge_request',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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]"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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' } }
|
||||
|
|
|
@ -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
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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) }
|
||||
|
|
8
config/feature_flags/development/updated_mr_header.yml
Normal file
8
config/feature_flags/development/updated_mr_header.yml
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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. | |
|
||||
|
||||
|
|
|
@ -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 | |
|
||||
|
||||
|
|
|
@ -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:
|
||||
##
|
||||
|
|
|
@ -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:
|
||||
##
|
||||
|
|
|
@ -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 | |
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
56
lib/gitlab/database/migrations/base_background_runner.rb
Normal file
56
lib/gitlab/database/migrations/base_background_runner.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
|
@ -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'))
|
||||
|
|
43
spec/support/helpers/database/migration_testing_helpers.rb
Normal file
43
spec/support/helpers/database/migration_testing_helpers.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue