Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-26 15:10:26 +00:00
parent ee0b7522d9
commit 60a260df41
66 changed files with 383 additions and 1155 deletions

View File

@ -536,7 +536,7 @@ gem 'valid_email', '~> 0.1'
# JSON
gem 'json', '~> 2.5.1'
gem 'json_schemer', '~> 0.2.18'
gem 'oj', '~> 3.13.17'
gem 'oj', '~> 3.13.18'
gem 'multi_json', '~> 1.14.1'
gem 'yajl-ruby', '~> 1.4.1', require: 'yajl'

View File

@ -869,7 +869,7 @@ GEM
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
oj (3.13.17)
oj (3.13.18)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -1637,7 +1637,7 @@ DEPENDENCIES
oauth2 (~> 2.0)
octokit (~> 4.15)
ohai (~> 16.10)
oj (~> 3.13.17)
oj (~> 3.13.18)
omniauth (~> 1.8)
omniauth-alicloud (~> 1.0.1)
omniauth-atlassian-oauth2 (~> 0.2.0)

View File

@ -170,7 +170,7 @@ export default {
return this.targetType === 'issue';
},
canAssign() {
return this.getNoteableData.current_user?.can_update && this.isIssue;
return this.getNoteableData.current_user?.can_set_issue_metadata && this.isIssue;
},
displayAuthorBadgeText() {
return sprintf(__('This user is the author of this %{noteable}.'), {

View File

@ -52,31 +52,20 @@ export default {
return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount);
},
message() {
if (this.glFeatures.restructuredMrWidget) {
if (this.state === 'closed') {
return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.');
} else if (this.isMerged) {
return s__(
'mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}.',
);
}
return this.isFastForwardEnabled
? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
: s__(
'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.',
);
if (this.state === 'closed') {
return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.');
} else if (this.isMerged) {
return s__(
'mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}.',
);
}
return this.isFastForwardEnabled
? s__('mrWidgetCommitsAdded|Adds %{commitCount} to %{targetBranch}.')
? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
: s__(
'mrWidgetCommitsAdded|Adds %{commitCount} and %{mergeCommitCount} to %{targetBranch}%{squashedCommits}.',
'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.',
);
},
textDecorativeComponent() {
return this.glFeatures.restructuredMrWidget ? 'span' : 'strong';
},
squashCommitMessage() {
if (this.isMerged) {
return s__('mergedCommitsAdded|(commits were squashed)');
@ -93,25 +82,19 @@ export default {
<span>
<gl-sprintf :message="message">
<template #commitCount>
<component :is="textDecorativeComponent" class="commits-count-message">{{
commitsCountMessage
}}</component>
<span class="commits-count-message">{{ commitsCountMessage }}</span>
</template>
<template #mergeCommitCount>
<component :is="textDecorativeComponent">{{ $options.mergeCommitCount }}</component>
<span>{{ $options.mergeCommitCount }}</span>
</template>
<template #targetBranch>
<span class="label-branch">{{ targetBranchEscaped }}</span>
</template>
<template #squashedCommits>
<template v-if="glFeatures.restructuredMrWidget && isSquashEnabled">
{{ squashCommitMessage }}</template
></template
>
<template v-if="isSquashEnabled"> {{ squashCommitMessage }}</template>
</template>
<template #mergeCommitSha>
<template v-if="glFeatures.restructuredMrWidget"
><span class="label-branch">{{ mergeCommitSha }}</span></template
>
<span class="label-branch">{{ mergeCommitSha }}</span>
</template>
</gl-sprintf>
</span>

View File

@ -1,7 +1,6 @@
<script>
import { GlSafeHtmlDirective as SafeHtml, GlLink, GlSprintf } from '@gitlab/ui';
import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
import { s__, n__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'MRWidgetRelatedLinks',
@ -10,9 +9,7 @@ export default {
},
components: {
GlLink,
GlSprintf,
},
mixins: [glFeatureFlagMixin()],
props: {
relatedLinks: {
type: Object,
@ -67,42 +64,21 @@ export default {
</script>
<template>
<section>
<p
v-if="relatedLinks.closing"
:class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }"
>
<p v-if="relatedLinks.closing" class="gl-display-inline gl-m-0">
{{ closesText }}
<span v-safe-html="relatedLinks.closing"></span>
</p>
<p
v-if="relatedLinks.mentioned"
:class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }"
>
<span v-if="relatedLinks.closing && glFeatures.restructuredMrWidget">&middot;</span>
<p v-if="relatedLinks.mentioned" class="gl-display-inline gl-m-0">
<span v-if="relatedLinks.closing">&middot;</span>
{{ n__('mrWidget|Mentions issue', 'mrWidget|Mentions issues', relatedLinks.mentionedCount) }}
<span v-safe-html="relatedLinks.mentioned"></span>
</p>
<p
v-if="shouldShowAssignToMeLink"
:class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }"
>
<p v-if="shouldShowAssignToMeLink" class="gl-display-inline gl-m-0">
<span>
<gl-link rel="nofollow" data-method="post" :href="relatedLinks.assignToMe">{{
assignIssueText
}}</gl-link>
</span>
</p>
<div
v-if="divergedCommitsCount > 0 && !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>

View File

@ -1,25 +1,17 @@
<script>
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlLoadingIcon } from '@gitlab/ui';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
export default {
components: {
ciIcon,
GlButton,
GlLoadingIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
status: {
type: String,
required: true,
},
showDisabledButton: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isLoading() {
@ -42,15 +34,5 @@ export default {
</div>
<ci-icon v-else :status="statusObj" :size="24" />
</div>
<gl-button
v-if="!glFeatures.restructuredMrWidget && showDisabledButton"
category="primary"
variant="confirm"
data-testid="disabled-merge-button"
:disabled="true"
>
{{ s__('mrWidget|Merge') }}
</gl-button>
</div>
</template>

View File

@ -1,8 +1,5 @@
<script>
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
mixins: [glFeatureFlagMixin()],
props: {
value: {
type: String,
@ -23,10 +20,7 @@ export default {
<template>
<li>
<div class="commit-message-editor">
<div
:class="{ 'gl-mb-3': glFeatures.restructuredMrWidget }"
class="d-flex flex-wrap align-items-center justify-content-between"
>
<div class="d-flex flex-wrap align-items-center justify-content-between gl-mb-3">
<label class="col-form-label" :for="inputId">
<strong>{{ label }}</strong>
</label>

View File

@ -1,5 +1,4 @@
<script>
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
@ -7,7 +6,6 @@ export default {
components: {
statusIcon,
},
mixins: [glFeatureFlagMixin()],
};
</script>
<template>
@ -16,7 +14,7 @@ export default {
<status-icon status="warning" show-disabled-button />
</div>
<div class="media-body">
<span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<span class="gl-ml-0! gl-text-body! bold">
{{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }}
</span>
</div>

View File

@ -3,7 +3,6 @@ import { GlSkeletonLoader, GlIcon, GlButton, GlSprintf } from '@gitlab/ui';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
@ -78,19 +77,6 @@ export default {
autoMergeStrategy() {
return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).autoMergeStrategy;
},
canRemoveSourceBranch() {
const { currentUserId } = this.mr;
const mergeUserId = this.glFeatures.mergeRequestWidgetGraphql
? getIdFromGraphQLId(this.state.mergeUser?.id)
: this.mr.mergeUserId;
const canRemoveSourceBranch = this.glFeatures.mergeRequestWidgetGraphql
? this.state.userPermissions.removeSourceBranch
: this.mr.canRemoveSourceBranch;
return (
!this.shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId
);
},
},
methods: {
cancelAutomaticMerge() {
@ -175,24 +161,6 @@ export default {
{{ cancelButtonText }}
</gl-button>
</h4>
<section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p v-if="shouldRemoveSourceBranch">
{{ s__('mrWidget|Deletes the source branch') }}
</p>
<p v-else class="gl-display-flex">
<span class="gl-mr-3">{{ s__('mrWidget|Does not delete the source branch') }}</span>
<gl-button
v-if="canRemoveSourceBranch"
:loading="isRemovingSourceBranch"
size="small"
class="js-remove-source-branch"
data-testid="removeSourceBranchButton"
@click="removeSourceBranch"
>
{{ s__('mrWidget|Delete source branch') }}
</gl-button>
</p>
</section>
</div>
</template>
</div>

View File

@ -1,5 +1,4 @@
<script>
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
@ -7,14 +6,13 @@ export default {
components: {
statusIcon,
},
mixins: [glFeatureFlagMixin()],
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="loading" />
<div class="media-body space-children">
<span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<span class="gl-ml-0! gl-text-body! bold">
{{ s__('mrWidget|Checking if merge request can be merged…') }}
</span>
</div>

View File

@ -1,5 +1,4 @@
<script>
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue';
@ -9,7 +8,6 @@ export default {
MrWidgetAuthorTime,
statusIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
/* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
@ -30,13 +28,6 @@ export default {
:date-title="mr.metrics.closedAt"
:date-readable="mr.metrics.readableClosedAt"
/>
<section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p>
{{ s__('mrWidget|The changes were not merged into') }}
<a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a>
</p>
</section>
</div>
</div>
</template>

View File

@ -97,18 +97,14 @@ export default {
</gl-skeleton-loader>
</div>
<div v-else class="media-body space-children gl-display-flex gl-align-items-center">
<span
v-if="shouldBeRebased"
:class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="bold"
>
<span v-if="shouldBeRebased" class="gl-ml-0! gl-text-body! bold">
{{
s__(`mrWidget|Merge blocked: fast-forward merge is not possible.
To merge this request, first rebase locally.`)
}}
</span>
<template v-else>
<span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<span class="gl-ml-0! gl-text-body! bold">
{{ s__('mrWidget|Merge blocked: merge conflicts must be resolved.') }}
<span v-if="!canMerge">
{{
@ -121,14 +117,14 @@ export default {
<gl-button
v-if="showResolveButton"
:href="mr.conflictResolutionPath"
:size="glFeatures.restructuredMrWidget ? 'small' : 'medium'"
size="small"
data-testid="resolve-conflicts-button"
>
{{ s__('mrWidget|Resolve conflicts') }}
</gl-button>
<gl-button
v-if="canMerge"
:size="glFeatures.restructuredMrWidget ? 'small' : 'medium'"
size="small"
data-testid="merge-locally-button"
class="js-check-out-modal-trigger"
>

View File

@ -1,13 +1,10 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import api from '~/api';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
import modalEventHub from '~/projects/commit/event_hub';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import eventHub from '../../event_hub';
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
@ -19,11 +16,8 @@ export default {
components: {
MrWidgetAuthorTime,
GlIcon,
ClipboardButton,
GlLoadingIcon,
GlButton,
},
mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@ -183,41 +177,6 @@ export default {
{{ s__('mrWidget|Delete source branch') }}
</gl-button>
</div>
<section
v-if="!glFeatures.restructuredMrWidget"
class="mr-info-list"
data-qa-selector="merged_status_content"
>
<p>
{{ s__('mrWidget|The changes were merged into') }}
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
<template v-if="mr.mergeCommitSha">
with
<a
:href="mr.mergeCommitPath"
class="commit-sha js-mr-merged-commit-sha"
v-text="mr.shortMergeCommitSha"
>
</a>
<clipboard-button
:title="__('Copy commit SHA')"
:text="mr.mergeCommitSha"
css-class="js-mr-merged-copy-sha"
category="tertiary"
size="small"
/>
</template>
</p>
<p v-if="mr.sourceBranchRemoved">
{{ s__('mrWidget|The source branch has been deleted') }}
</p>
<p v-if="shouldShowSourceBranchRemoving">
<gl-loading-icon size="sm" :inline="true" />
<span> {{ s__('mrWidget|The source branch is being deleted') }} </span>
</p>
</section>
</div>
</div>
</template>

View File

@ -1,6 +1,5 @@
<script>
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '~/merge_request';
import eventHub from '../../event_hub';
@ -15,7 +14,6 @@ export default {
components: {
statusIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@ -90,14 +88,6 @@ export default {
{{ mergeStatus.message }}
<gl-emoji :data-name="mergeStatus.emoji" />
</h4>
<section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p>
{{ s__('mrWidget|Merges changes into') }}
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
</p>
</section>
</div>
</div>
</template>

View File

@ -74,13 +74,7 @@ export default {
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span
:class="{
'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget,
}"
class="bold js-branch-text"
data-testid="widget-content"
>
<span class="gl-ml-0! gl-text-body! bold js-branch-text" data-testid="widget-content">
<gl-sprintf :message="warning">
<template #code="{ content }">
<code>{{ content }}</code>

View File

@ -14,7 +14,7 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<span class="gl-ml-0! gl-text-body! bold">
{{
s__(
`mrWidget|Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.`,

View File

@ -161,15 +161,13 @@ export default {
<div class="rebase-state-find-class-convention media media-body space-children">
<span
v-if="rebaseInProgress || isMakingRequest"
:class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
class="gl-ml-0! gl-text-body! gl-font-weight-bold"
data-testid="rebase-message"
>{{ __('Rebase in progress') }}</span
>
<span
v-if="!rebaseInProgress && !canPushToSourceBranch"
:class="{ 'gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold gl-ml-0!"
class="gl-text-body! gl-font-weight-bold gl-ml-0!"
data-testid="rebase-message"
>{{ fastForwardMergeText }}</span
>
@ -177,30 +175,9 @@ export default {
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children gl-align-items-center"
>
<gl-button
v-if="!glFeatures.restructuredMrWidget"
:loading="isMakingRequest"
variant="confirm"
data-qa-selector="mr_rebase_button"
data-testid="standard-rebase-button"
@click="rebase"
>
{{ __('Rebase') }}
</gl-button>
<gl-button
v-if="!glFeatures.restructuredMrWidget && showRebaseWithoutCi"
:loading="isMakingRequest"
variant="confirm"
category="secondary"
data-testid="rebase-without-ci-button"
@click="rebaseWithoutCi"
>
{{ __('Rebase without pipeline') }}
</gl-button>
<span
v-if="!rebasingError"
:class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
class="gl-ml-0! gl-text-body! gl-font-weight-bold"
data-testid="rebase-message"
data-qa-selector="no_fast_forward_message_content"
>{{
@ -211,18 +188,18 @@ export default {
rebasingError
}}</span>
<gl-button
v-if="glFeatures.restructuredMrWidget"
:loading="isMakingRequest"
variant="confirm"
size="small"
data-qa-selector="mr_rebase_button"
data-testid="standard-rebase-button"
class="gl-ml-3!"
@click="rebase"
>
{{ __('Rebase') }}
</gl-button>
<gl-button
v-if="glFeatures.restructuredMrWidget && showRebaseWithoutCi"
v-if="showRebaseWithoutCi"
:loading="isMakingRequest"
variant="confirm"
size="small"

View File

@ -1,7 +1,6 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import statusIcon from '../mr_widget_status_icon.vue';
@ -12,7 +11,6 @@ export default {
GlSprintf,
statusIcon,
},
mixins: [glFeatureFlagMixin()],
computed: {
troubleshootingDocsPath() {
return helpPagePath('ci/troubleshooting', { anchor: 'merge-request-status-messages' });
@ -30,7 +28,7 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<span class="gl-ml-0! gl-text-body! bold">
<gl-sprintf :message="$options.i18n.failedMessage">
<template #link="{ content }">
<gl-link :href="troubleshootingDocsPath" target="_blank">

View File

@ -31,12 +31,10 @@ import {
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import MergeRequestStore from '../../stores/mr_widget_store';
import statusIcon from '../mr_widget_status_icon.vue';
import AddedCommitMessage from '../added_commit_message.vue';
import RelatedLinks from '../mr_widget_related_links.vue';
import CommitEdit from './commit_edit.vue';
import CommitMessageDropdown from './commit_message_dropdown.vue';
import CommitsHeader from './commits_header.vue';
import SquashBeforeMerge from './squash_before_merge.vue';
import MergeFailedPipelineConfirmationDialog from './merge_failed_pipeline_confirmation_dialog.vue';
@ -96,9 +94,7 @@ export default {
},
},
components: {
statusIcon,
SquashBeforeMerge,
CommitsHeader,
CommitEdit,
CommitMessageDropdown,
GlIcon,
@ -320,34 +316,24 @@ export default {
showDangerMessageForMergeTrain() {
return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed;
},
restructuredWidgetShowMergeButtons() {
if (this.glFeatures.restructuredMrWidget) {
return (
(this.isMergeAllowed || this.isAutoMergeAvailable) &&
this.state.userPermissions.canMerge &&
!this.mr.mergeOngoing &&
!this.mr.autoMergeEnabled
);
}
return true;
shouldShowMergeControls() {
return (
(this.isMergeAllowed || this.isAutoMergeAvailable) &&
(this.stateData.userPermissions?.canMerge || this.mr.canMerge) &&
!this.mr.mergeOngoing &&
!this.mr.autoMergeEnabled
);
},
sourceBranchDeletedText() {
if (this.glFeatures.restructuredMrWidget) {
if (this.removeSourceBranch) {
return this.mr.state === 'merged'
? __('Deleted the source branch.')
: __('Source branch will be deleted.');
}
if (this.removeSourceBranch) {
return this.mr.state === 'merged'
? __('Did not delete the source branch.')
: __('Source branch will not be deleted.');
? __('Deleted the source branch.')
: __('Source branch will be deleted.');
}
return this.removeSourceBranch
? __('Deletes the source branch.')
: __('Does not delete the source branch.');
return this.mr.state === 'merged'
? __('Did not delete the source branch.')
: __('Source branch will not be deleted.');
},
},
mounted() {
@ -525,10 +511,7 @@ export default {
<template>
<div
data-testid="ready_to_merge_state"
:class="{
'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base':
glFeatures.restructuredMrWidget,
}"
class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
>
<div v-if="loading" class="mr-widget-body">
<div class="gl-w-full mr-ready-to-merge-loader">
@ -541,16 +524,10 @@ export default {
</div>
</div>
<template v-else>
<div
class="mr-widget-body media"
:class="{
'mr-widget-body-line-height-1': glFeatures.restructuredMrWidget,
}"
>
<status-icon v-if="!glFeatures.restructuredMrWidget" :status="iconClass" />
<div class="mr-widget-body media mr-widget-body-line-height-1">
<div class="media-body">
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap">
<gl-button-group v-if="restructuredWidgetShowMergeButtons" class="gl-align-self-start">
<gl-button-group v-if="shouldShowMergeControls" class="gl-align-self-start">
<gl-button
size="medium"
category="primary"
@ -603,19 +580,14 @@ export default {
<merge-train-helper-icon v-if="shouldRenderMergeTrainHelperIcon" class="gl-mx-3" />
<div
v-if="shouldShowMergeControls"
:class="{ 'gl-w-full gl-order-n1 gl-mb-5': glFeatures.restructuredMrWidget }"
class="gl-display-flex gl-align-items-center gl-flex-wrap"
class="gl-display-flex gl-align-items-center gl-flex-wrap gl-w-full gl-order-n1 gl-mb-5"
>
<gl-form-checkbox
v-if="canRemoveSourceBranch"
id="remove-source-branch-input"
v-model="removeSourceBranch"
:disabled="isRemoveSourceBranchButtonDisabled"
:class="{
'gl-mx-3': !glFeatures.restructuredMrWidget,
'gl-mr-5': glFeatures.restructuredMrWidget,
}"
class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center"
class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center gl-mr-5"
>
{{ __('Delete source branch') }}
</gl-form-checkbox>
@ -626,16 +598,11 @@ export default {
v-model="squashBeforeMerge"
:help-path="mr.squashBeforeMergeHelpPath"
:is-disabled="isSquashReadOnly"
:class="{
'gl-mx-3': !glFeatures.restructuredMrWidget,
'gl-mr-5': glFeatures.restructuredMrWidget,
}"
class="gl-mr-5"
/>
<gl-form-checkbox
v-if="
glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)
"
v-if="shouldShowSquashEdit || shouldShowMergeEdit"
v-model="editCommitMessage"
data-testid="widget_edit_commit_message"
class="gl-display-flex gl-align-items-center"
@ -644,198 +611,113 @@ export default {
</gl-form-checkbox>
</div>
<div
v-else-if="!glFeatures.restructuredMrWidget"
class="bold js-resolve-mr-widget-items-message gl-ml-3"
v-if="editCommitMessage"
class="gl-w-full gl-order-n1"
data-testid="edit_commit_message"
>
<div
v-if="hasPipelineMustSucceedConflict"
class="gl-display-flex gl-align-items-center"
data-testid="pipeline-succeed-conflict"
>
<gl-sprintf :message="pipelineMustSucceedConflictText" />
<gl-link
:href="mr.pipelineMustSucceedDocsPath"
target="_blank"
class="gl-display-flex gl-ml-2"
<ul class="border-top commits-list flex-list gl-list-style-none gl-p-0 gl-pt-4">
<commit-edit
v-if="shouldShowSquashEdit"
:value="squashCommitMessage"
:label="__('Squash commit message')"
input-id="squash-message-edit"
class="gl-m-0! gl-p-0!"
@input="setSquashCommitMessage"
>
<gl-icon name="question" />
</gl-link>
</div>
<gl-sprintf v-else :message="mergeDisabledText" />
</div>
<template v-if="glFeatures.restructuredMrWidget">
<div
v-if="editCommitMessage"
class="gl-w-full gl-order-n1"
data-testid="edit_commit_message"
>
<ul
:class="{
'content-list': !glFeatures.restructuredMrWidget,
'gl-list-style-none gl-p-0 gl-pt-4': glFeatures.restructuredMrWidget,
}"
class="border-top commits-list flex-list"
>
<commit-edit
v-if="shouldShowSquashEdit"
:value="squashCommitMessage"
:label="__('Squash commit message')"
input-id="squash-message-edit"
class="gl-m-0! gl-p-0!"
@input="setSquashCommitMessage"
>
<template #header>
<commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" />
</template>
</commit-edit>
<commit-edit
v-if="shouldShowMergeEdit"
:value="commitMessage"
:label="__('Merge commit message')"
input-id="merge-message-edit"
class="gl-m-0! gl-p-0!"
@input="setCommitMessage"
/>
<li class="gl-m-0! gl-p-0!">
<p class="form-text text-muted">
<gl-sprintf :message="commitTemplateHintText">
<template #link="{ content }">
<gl-link
:href="commitTemplateHelpPage"
class="inline-link"
target="_blank"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</li>
</ul>
</div>
<div
v-if="!restructuredWidgetShowMergeButtons"
class="gl-w-full gl-order-n1 gl-text-gray-500"
data-qa-selector="merged_status_content"
>
<strong v-if="mr.state !== 'closed'">
{{ __('Merge details') }}
</strong>
<ul class="gl-pl-4 gl-m-0">
<li v-if="mr.divergedCommitsCount > 0" 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 #header>
<commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" />
</template>
</commit-edit>
<commit-edit
v-if="shouldShowMergeEdit"
:value="commitMessage"
:label="__('Merge commit message')"
input-id="merge-message-edit"
class="gl-m-0! gl-p-0!"
@input="setCommitMessage"
/>
<li class="gl-m-0! gl-p-0!">
<p class="form-text text-muted">
<gl-sprintf :message="commitTemplateHintText">
<template #link="{ content }">
<gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</li>
<li class="gl-line-height-normal">
<added-commit-message
:state="mr.state"
:merge-commit-sha="mr.shortMergeCommitSha"
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
/>
</li>
<li v-if="mr.state !== 'closed'" class="gl-line-height-normal">
{{ sourceBranchDeletedText }}
</li>
<li v-if="mr.relatedLinks" class="gl-line-height-normal">
<related-links
:state="mr.state"
:related-links="mr.relatedLinks"
:show-assign-to-me="false"
class="mr-ready-merge-related-links gl-display-inline"
/>
</li>
</ul>
</div>
<div
v-else
:class="{ 'gl-mb-5': restructuredWidgetShowMergeButtons }"
class="gl-w-full gl-order-n1 gl-text-gray-500"
>
<added-commit-message
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
/>
<template v-if="mr.relatedLinks">
&middot;
</p>
</li>
</ul>
</div>
<div
v-if="!shouldShowMergeControls"
class="gl-w-full gl-order-n1 gl-text-gray-500"
data-qa-selector="merged_status_content"
>
<strong v-if="mr.state !== 'closed'">
{{ __('Merge details') }}
</strong>
<ul class="gl-pl-4 gl-m-0">
<li v-if="mr.divergedCommitsCount > 0" 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"
:merge-commit-sha="mr.shortMergeCommitSha"
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
/>
</li>
<li v-if="mr.state !== 'closed'" class="gl-line-height-normal">
{{ sourceBranchDeletedText }}
</li>
<li v-if="mr.relatedLinks" class="gl-line-height-normal">
<related-links
: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>
</div>
</template>
</div>
<div
v-if="showDangerMessageForMergeTrain && !glFeatures.restructuredMrWidget"
class="gl-mt-5 gl-text-gray-500"
data-testid="failed-pipeline-merge-train-text"
>
{{ __('The latest pipeline for this merge request did not complete successfully.') }}
</li>
</ul>
</div>
<div
v-else
:class="{ 'gl-mb-5': shouldShowMergeControls }"
class="gl-w-full gl-order-n1 gl-text-gray-500"
>
<added-commit-message
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
/>
<template v-if="mr.relatedLinks">
&middot;
<related-links
: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>
</div>
</div>
</div>
</div>
<template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget">
<div v-if="!shouldShowMergeEdit" class="mr-fast-forward-message">
{{ __('Fast-forward merge without a merge commit') }}
</div>
<commits-header
v-if="!glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)"
:is-squash-enabled="squashBeforeMerge"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:class="{ 'border-bottom': stateData.mergeError }"
>
<ul class="border-top content-list commits-list flex-list">
<commit-edit
v-if="shouldShowSquashEdit"
:value="squashCommitMessage"
:label="__('Squash commit message')"
input-id="squash-message-edit"
squash
@input="setSquashCommitMessage"
>
<template #header>
<commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" />
</template>
</commit-edit>
<commit-edit
v-if="shouldShowMergeEdit"
:value="commitMessage"
:label="__('Merge commit message')"
input-id="merge-message-edit"
@input="setCommitMessage"
/>
<li>
<p class="form-text text-muted">
<gl-sprintf :message="commitTemplateHintText">
<template #link="{ content }">
<gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</li>
</ul>
</commits-header>
</template>
</template>
</div>
</template>

View File

@ -1,6 +1,5 @@
<script>
import { GlButton } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { I18N_SHA_MISMATCH } from '../../i18n';
import statusIcon from '../mr_widget_status_icon.vue';
@ -13,7 +12,6 @@ export default {
i18n: {
I18N_SHA_MISMATCH,
},
mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@ -28,8 +26,7 @@ export default {
<status-icon :show-disabled-button="false" status="warning" />
<div class="media-body">
<span
:class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
class="gl-ml-0! gl-text-body! gl-font-weight-bold"
data-qa-selector="head_mismatch_content"
>
{{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }}

View File

@ -1,6 +1,5 @@
<script>
import { GlIcon, GlTooltipDirective, GlFormCheckbox, GlLink } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SQUASH_BEFORE_MERGE } from '../../i18n';
export default {
@ -12,7 +11,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
i18n: {
...SQUASH_BEFORE_MERGE,
},
@ -36,9 +34,6 @@ export default {
tooltipTitle() {
return this.isDisabled ? this.$options.i18n.tooltipTitle : null;
},
helpIconName() {
return this.glFeatures.restructuredMrWidget ? 'question-o' : 'question';
},
},
};
</script>
@ -62,10 +57,10 @@ export default {
v-gl-tooltip
:href="helpPath"
:title="$options.i18n.helpLabel"
:class="{ 'gl-text-blue-600': glFeatures.restructuredMrWidget }"
class="gl-text-blue-600"
target="_blank"
>
<gl-icon :name="helpIconName" />
<gl-icon name="question-o" />
<span class="sr-only">
{{ $options.i18n.helpLabel }}
</span>

View File

@ -1,6 +1,5 @@
<script>
import { GlButton } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import notesEventHub from '~/notes/event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
@ -10,7 +9,6 @@ export default {
statusIcon,
GlButton,
},
mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@ -29,22 +27,15 @@ export default {
<div class="mr-widget-body media gl-flex-wrap">
<status-icon show-disabled-button status="warning" />
<div class="media-body">
<span
:class="{
'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget,
'gl-display-block': !glFeatures.restructuredMrWidget,
}"
class="gl-ml-3 gl-font-weight-bold gl-w-100"
>
<span class="gl-ml-0! gl-text-body! gl-ml-3 gl-font-weight-bold gl-w-100">
{{ s__('mrWidget|Merge blocked: all threads must be resolved.') }}
</span>
<gl-button
data-testid="jump-to-first"
class="gl-ml-3"
size="small"
:icon="glFeatures.restructuredMrWidget ? undefined : 'comment-next'"
:variant="glFeatures.restructuredMrWidget ? 'confirm' : 'default'"
:category="glFeatures.restructuredMrWidget ? 'secondary' : 'primary'"
variant="confirm"
category="secondary"
@click="jumpToFirstUnresolvedDiscussion"
>
{{ s__('mrWidget|Jump to first unresolved thread') }}
@ -54,7 +45,6 @@ export default {
:href="mr.createIssueToResolveDiscussionsPath"
class="js-create-issue gl-ml-3"
size="small"
:icon="glFeatures.restructuredMrWidget ? undefined : 'issue-new'"
>
{{ s__('mrWidget|Create issue to resolve all threads') }}
</gl-button>

View File

@ -167,10 +167,7 @@ export default {
<status-icon :show-disabled-button="canUpdate" status="warning" />
<div class="media-body">
<div class="float-left">
<span
:class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
>
<span class="gl-ml-0! gl-text-body! gl-font-weight-bold">
{{
__("Merge blocked: merge request must be marked as ready. It's still marked as draft.")
}}

View File

@ -20,13 +20,6 @@ export default {
this.mr.preventMerge,
);
},
shouldShowMergeControls() {
if (this.glFeatures.restructuredMrWidget) {
return this.restructuredWidgetShowMergeButtons;
}
return this.isMergeAllowed || this.isAutoMergeAvailable;
},
mergeDisabledText() {
if (this.pipeline?.status === PIPELINE_SKIPPED_STATUS) {
return MERGE_DISABLED_SKIPPED_PIPELINE_TEXT;

View File

@ -17,7 +17,6 @@ import { setFaviconOverlay } from '../lib/utils/favicon';
import Loading from './components/loading.vue';
import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue';
import WidgetRelatedLinks from './components/mr_widget_related_links.vue';
import WidgetSuggestPipeline from './components/mr_widget_suggest_pipeline.vue';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import ArchivedState from './components/states/mr_widget_archived.vue';
@ -61,7 +60,6 @@ export default {
ExtensionsContainer,
'mr-widget-suggest-pipeline': WidgetSuggestPipeline,
MrWidgetPipelineContainer,
'mr-widget-related-links': WidgetRelatedLinks,
MrWidgetAlertMessage,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
@ -73,9 +71,7 @@ export default {
'mr-widget-nothing-to-merge': NothingToMergeState,
'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': window.gon?.features?.restructuredMrWidget
? () => import('./components/states/new_ready_to_merge.vue')
: ReadyToMergeState,
'mr-widget-ready-to-merge': () => import('./components/states/new_ready_to_merge.vue'),
'sha-mismatch': ShaMismatch,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
@ -163,12 +159,6 @@ export default {
shouldRenderCodeQuality() {
return this.mr?.codequalityReportsPath;
},
shouldRenderRelatedLinks() {
return (
(Boolean(this.mr.relatedLinks) || this.mr.divergedCommitsCount > 0) &&
!this.mr.isNothingToMergeState
);
},
shouldRenderSourceBranchRemovalStatus() {
return (
!this.mr.canRemoveSourceBranch &&
@ -239,9 +229,6 @@ export default {
shouldShowCodeQualityExtension() {
return window.gon?.features?.refactorCodeQualityExtension;
},
isRestructuredMrWidgetEnabled() {
return window.gon?.features?.restructuredMrWidget;
},
},
watch: {
'mr.machineValue': {
@ -638,23 +625,7 @@ export default {
<div class="mr-widget-section" data-qa-selector="mr_widget_content">
<component :is="componentName" :mr="mr" :service="service" />
<ready-to-merge
v-if="isRestructuredMrWidgetEnabled && mr.commitsCount"
:mr="mr"
:service="service"
/>
<div v-else class="mr-widget-info">
<mr-widget-related-links
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"
/>
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
</div>
<ready-to-merge v-if="mr.commitsCount" :mr="mr" :service="service" />
</div>
</div>
<mr-widget-pipeline-container

View File

@ -27,8 +27,6 @@ export default function deviseState() {
return stateKey.shaMismatch;
} else if (this.autoMergeEnabled && !this.mergeError) {
return stateKey.autoMergeEnabled;
} else if (!this.canMerge && !window.gon?.features?.restructuredMrWidget) {
return stateKey.notAllowedToMerge;
} else if (this.canBeMerged) {
return stateKey.readyToMerge;
}

View File

@ -34,7 +34,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action only: [:show] do
push_frontend_feature_flag(:merge_request_widget_graphql, project)
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:restructured_mr_widget, project)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, project)
push_frontend_feature_flag(:refactor_code_quality_extension, project)
push_frontend_feature_flag(:refactor_mr_widget_test_summary, project)

View File

@ -47,6 +47,10 @@ class IssueEntity < IssuableEntity
can?(request.current_user, :update_issue, issue)
end
expose :can_set_issue_metadata do |issue|
can?(request.current_user, :set_issue_metadata, issue)
end
expose :can_award_emoji do |issue|
can?(request.current_user, :award_emoji, issue)
end

View File

@ -4,7 +4,8 @@
= s_('GitLabPages|Configure pages')
.card-body
%p.gl-mb-0
- docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer'>".html_safe
- samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer'>".html_safe
- docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_docs_link'>".html_safe
- samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_samples_link>"
.html_safe
- link_end = '</a>'.html_safe
= s_('GitLabPages|Your Pages site is not configured yet. See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also take some inspiration from the %{samples_link_start}sample Pages projects%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, link_end: link_end }

View File

@ -3,7 +3,7 @@
- project = @target_project || @project
- presenter = local_assigns.fetch(:presenter, nil)
= form_errors(issuable)
= form_errors(issuable, pajamas_alert: true)
- if @conflict
= render Pajamas::AlertComponent.new(variant: :danger,

View File

@ -7,7 +7,7 @@ module WaitableWorker
# Schedules multiple jobs and waits for them to be completed.
def bulk_perform_and_wait(args_list)
# Short-circuit: it's more efficient to do small numbers of jobs inline
if args_list.size == 1
if args_list.size == 1 && !always_async_project_authorizations_refresh?
return bulk_perform_inline(args_list)
end
@ -29,6 +29,10 @@ module WaitableWorker
bulk_perform_async(failed) if failed.present?
end
def always_async_project_authorizations_refresh?
Feature.enabled?(:always_async_project_authorizations_refresh)
end
end
def perform(*args)

View File

@ -1,8 +1,8 @@
---
name: restructured_mr_widget
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68565
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339181
milestone: '14.3'
name: always_async_project_authorizations_refresh
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92333
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367683
milestone: '15.3'
type: development
group: group::code review
default_enabled: true
group: group::workspace
default_enabled: false

View File

@ -23,7 +23,7 @@ The following lists the currently supported OSs and their possible EOL dates.
| Debian 10 | GitLab CE / GitLab EE 12.2.0 | amd64, arm64 | [Debian Install Documentation](https://about.gitlab.com/install/#debian) | 2024 | <https://wiki.debian.org/LTS> |
| Debian 11 | GitLab CE / GitLab EE 14.6.0 | amd64, arm64 | [Debian Install Documentation](https://about.gitlab.com/install/#debian) | 2026 | <https://wiki.debian.org/LTS> |
| OpenSUSE 15.3 | GitLab CE / GitLab EE 14.5.0 | x86_64, aarch64 | [OpenSUSE Install Documentation](https://about.gitlab.com/install/#opensuse-leap-15-3) | Nov 2022 | <https://en.opensuse.org/Lifetime> |
| RHEL 8 | GitLab CE / GitLab EE 12.8.1 | x86_64, arm64 | [Use CentOS Install Documentation](https://about.gitlab.com/install/#centos-7) | May 2024 | [RHEL Details](https://access.redhat.com/support/policy/updates/errata/#Life_Cycle_Dates) |
| RHEL 8 | GitLab CE / GitLab EE 12.8.1 | x86_64, arm64 | [Use CentOS Install Documentation](https://about.gitlab.com/install/#centos-7) | May 2029 | [RHEL Details](https://access.redhat.com/support/policy/updates/errata/#Life_Cycle_Dates) |
| SLES 12 | GitLab EE 9.0.0 | x86_64 | [Use OpenSUSE Install Documentation](https://about.gitlab.com/install/#opensuse-leap-15-3) | Oct 2027 | <https://www.suse.com/lifecycle/> |
| Oracle Linux | GitLab CE / GitLab EE 8.14.0 | x86_64 | [Use CentOS Install Documentation](https://about.gitlab.com/install/#centos-7) | Jul 2024 | <https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf> |
| Scientific Linux | GitLab CE / GitLab EE 8.14.0 | x86_64 | [Use CentOS Install Documentation](https://about.gitlab.com/install/#centos-7) | June 2024 | <https://scientificlinux.org/downloads/sl-versions/sl7/> |

View File

@ -40,6 +40,10 @@ is invalid, a tip is shown to help you fix the problem:
## Lint CI configuration
NOTE:
The **Lint** tab is replaced with the **Validate** tab when [pipeline simulations](#simulate-a-cicd-pipeline)
are enabled.
To test the validity of your GitLab CI/CD configuration before committing the changes,
you can use the CI lint tool. To access it, go to **CI/CD > Editor** and select the **Lint** tab.
@ -51,6 +55,20 @@ reflected in the CI lint. It displays the same results as the existing [CI Lint
![Linting errors in a CI configuration](img/pipeline_editor_lint_v13_8.png)
## Simulate a CI/CD pipeline
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/337282) in GitLab 15.3 [with a flag](../../administration/feature_flags.md) named `simulate_pipeline`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `simulate_pipeline`.
The feature is not ready for production use. When this feature is enabled, it replaces the **Lint** tab.
To look for pipeline syntax and logic issues, you can simulate the creation of a
GitLab CI/CD pipeline in the **Validate** tab. A pipeline simulation can help find
problems such as incorrect `rules` and `needs` job dependencies, and is similar to
simulations in the [CI Lint tool](../lint.md#simulate-a-pipeline).
## View included CI/CD configuration
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7064) in GitLab 15.0 [with a flag](../../administration/feature_flags.md) named `pipeline_editor_file_tree`. Disabled by default.

View File

@ -104,6 +104,9 @@ for the section. For example:
> `widget_message` [introduced](<link-to-issue>) in GitLab 14.3.
```
If the API or attribute is deployed behind a feature flag,
[include the feature flag information](feature_flags.md) in the version history.
## Deprecations
To document the deprecation of an API endpoint, follow the steps to

View File

@ -66,10 +66,10 @@ you should keep the documentation with the code in that repository.
Then you can use one of these approaches:
- (Recommended) [Add the repository to the list of products](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/development.md#add-a-new-product)
- Recommended. [Add the repository to the list of products](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/development.md#add-a-new-product)
published at <https://docs.gitlab.com>. The source of the documentation pages remains
in the external repository, but the resulting pages are indexed and searchable on <https://docs.gitlab.com>.
- (Recommended) [Add an entry in the global navigation](global_nav.md#add-a-navigation-entry) for
- Recommended. [Add an entry in the global navigation](global_nav.md#add-a-navigation-entry) for
<https://docs.gitlab.com> that links directly to the documentation in that external repository.
The documentation pages are not indexed or searchable on <https://docs.gitlab.com>.
View [an example](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/fedb6378a3c92274ba3b6031df0d34455594e4cc/content/_data/navigation.yaml#L2944-L2946).

View File

@ -41,7 +41,7 @@ module Gitlab
end
log_report(report_label(report), tms)
@report_duration_counter.increment({ report: report_label(report) }, tms.real.to_i)
@report_duration_counter.increment({ report: report_label(report) }, tms.real)
sleep sleep_between_reports_s
end
@ -63,7 +63,7 @@ module Gitlab
message: 'finished',
pid: $$,
worker_id: worker_id,
report: report_label,
perf_report: report_label,
duration_s: tms.real.round(2),
cpu_s: tms.utime.round(2),
sys_cpu_s: tms.stime.round(2)

View File

@ -12466,9 +12466,6 @@ msgstr ""
msgid "Deletes the source branch"
msgstr ""
msgid "Deletes the source branch."
msgstr ""
msgid "Deleting"
msgstr ""
@ -13732,9 +13729,6 @@ msgstr ""
msgid "Documents reindexed: %{processed_documents} (%{percentage}%%)"
msgstr ""
msgid "Does not delete the source branch."
msgstr ""
msgid "Domain"
msgstr ""
@ -16021,9 +16015,6 @@ msgstr ""
msgid "Fast timeout"
msgstr ""
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster releases. Better code. Less pain."
msgstr ""
@ -38957,9 +38948,6 @@ msgstr ""
msgid "The latest artifacts created by jobs in the most recent successful pipeline will be stored."
msgstr ""
msgid "The latest pipeline for this merge request did not complete successfully."
msgstr ""
msgid "The latest pipeline for this merge request did not succeed. The latest changes are unverified."
msgstr ""
@ -39488,9 +39476,6 @@ msgstr ""
msgid "There was an error removing the e-mail."
msgstr ""
msgid "There was an error resetting group pipeline minutes."
msgstr ""
msgid "There was an error resetting user pipeline minutes."
msgstr ""
@ -46292,12 +46277,6 @@ msgstr ""
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
msgid "mrWidgetCommitsAdded|Adds %{commitCount} and %{mergeCommitCount} to %{targetBranch}%{squashedCommits}."
msgstr ""
msgid "mrWidgetCommitsAdded|Adds %{commitCount} to %{targetBranch}."
msgstr ""
msgid "mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}."
msgstr ""
@ -46414,9 +46393,6 @@ msgstr ""
msgid "mrWidget|Delete source branch"
msgstr ""
msgid "mrWidget|Deletes the source branch"
msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
@ -46426,9 +46402,6 @@ msgstr ""
msgid "mrWidget|Dismiss"
msgstr ""
msgid "mrWidget|Does not delete the source branch"
msgstr ""
msgid "mrWidget|Failed to load deployment statistics"
msgstr ""
@ -46464,9 +46437,6 @@ msgid_plural "mrWidget|Mentions issues"
msgstr[0] ""
msgstr[1] ""
msgid "mrWidget|Merge"
msgstr ""
msgid "mrWidget|Merge blocked: all required approvals must be given."
msgstr ""
@ -46500,9 +46470,6 @@ msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
msgid "mrWidget|Merges changes into"
msgstr ""
msgid "mrWidget|Merging! Changes are being shipped…"
msgstr ""
@ -46587,21 +46554,9 @@ msgstr ""
msgid "mrWidget|The %{type} branch %{codeStart}%{name}%{codeEnd} does not exist."
msgstr ""
msgid "mrWidget|The changes were merged into"
msgstr ""
msgid "mrWidget|The changes were not merged into"
msgstr ""
msgid "mrWidget|The source branch has been deleted"
msgstr ""
msgid "mrWidget|The source branch is %{link} the target branch"
msgstr ""
msgid "mrWidget|The source branch is being deleted"
msgstr ""
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr ""

View File

@ -71,7 +71,6 @@ module QA
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
element :cherry_pick_button
element :merged_status_content
element :revert_button
end

View File

@ -21,27 +21,6 @@ RSpec.describe "User merges a merge request", :js do
end
end
context "ff-only merge" do
let(:project) { create(:project, :public, :repository, merge_requests_ff_only_enabled: true) }
before do
stub_feature_flags(restructured_mr_widget: false)
visit(merge_request_path(merge_request))
end
context "when branch is rebased" do
let!(:merge_request) { create(:merge_request, :rebased, source_project: project) }
it_behaves_like "fast forward merge a merge request"
end
context "when branch is merged" do
let!(:merge_request) { create(:merge_request, :merged_target, source_project: project) }
it_behaves_like "fast forward merge a merge request"
end
end
context 'sidebar merge requests counter' do
let(:project) { create(:project, :public, :repository) }
let!(:merge_request) { create(:merge_request, source_project: project) }

View File

@ -159,7 +159,7 @@ describe('noteActions', () => {
});
});
describe('when a user has access to edit an issue', () => {
describe('when a user can set metadata of an issue', () => {
const testButtonClickTriggersAction = () => {
axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
expect(actions.updateAssignees).toHaveBeenCalled();
@ -176,7 +176,7 @@ describe('noteActions', () => {
});
store.state.noteableData = {
current_user: {
can_update: true,
can_set_issue_metadata: true,
},
};
store.state.userData = userDataMock;
@ -191,6 +191,31 @@ describe('noteActions', () => {
it('should be possible to unassign the comment author', testButtonClickTriggersAction);
});
describe('when a user can update but not set metadata of an issue', () => {
beforeEach(() => {
wrapper = mountNoteActions(props, {
targetType: () => 'issue',
});
store.state.noteableData = {
current_user: {
can_update: true,
can_set_issue_metadata: false,
},
};
store.state.userData = userDataMock;
});
afterEach(() => {
wrapper.destroy();
axiosMock.restore();
});
it('should not be possible to assign or unassign the comment author', () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
expect(assignUserButton.exists()).toBe(false);
});
});
describe('when a user does not have access to edit an issue', () => {
const testButtonDoesNotRender = () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');

View File

@ -10,11 +10,6 @@ function factory(propsData) {
targetBranch: 'main',
...propsData,
},
provide: {
glFeatures: {
restructuredMrWidget: true.valueOf,
},
},
});
}

View File

@ -6,7 +6,6 @@ describe('MR widget status icon component', () => {
let wrapper;
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findDisabledMergeButton = () => wrapper.find('[data-testid="disabled-merge-button"]');
const createWrapper = (props, mountFn = shallowMount) => {
wrapper = mountFn(mrStatusIcon, {
@ -41,20 +40,4 @@ describe('MR widget status icon component', () => {
expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true);
});
});
describe('with disabled button', () => {
it('renders a disabled button', () => {
createWrapper({ status: 'failed', showDisabledButton: true });
expect(findDisabledMergeButton().exists()).toBe(true);
});
});
describe('without disabled button', () => {
it('does not render a disabled button', () => {
createWrapper({ status: 'failed' });
expect(findDisabledMergeButton().exists()).toBe(false);
});
});
});

View File

@ -40,34 +40,6 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
</gl-button-stub>
</h4>
<section
class="mr-info-list"
>
<p
class="gl-display-flex"
>
<span
class="gl-mr-3"
>
Does not delete the source branch
</span>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-remove-source-branch"
data-testid="removeSourceBranchButton"
icon=""
size="small"
variant="default"
>
Delete source branch
</gl-button-stub>
</p>
</section>
</div>
</div>
`;
@ -112,34 +84,6 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c
</gl-button-stub>
</h4>
<section
class="mr-info-list"
>
<p
class="gl-display-flex"
>
<span
class="gl-mr-3"
>
Does not delete the source branch
</span>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-remove-source-branch"
data-testid="removeSourceBranchButton"
icon=""
size="small"
variant="default"
>
Delete source branch
</gl-button-stub>
</p>
</section>
</div>
</div>
`;

View File

@ -5,7 +5,7 @@ exports[`PipelineFailed should render error message with a disabled merge button
class="mr-widget-body media"
>
<status-icon-stub
showdisabledbutton="true"
show-disabled-button="true"
status="warning"
/>
@ -13,7 +13,7 @@ exports[`PipelineFailed should render error message with a disabled merge button
class="media-body space-children"
>
<span
class="bold"
class="gl-ml-0! gl-text-body! bold"
>
<gl-sprintf-stub
message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"

View File

@ -18,11 +18,6 @@ describe('MRWidgetArchived', () => {
expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
});
it('renders a disabled button', () => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge');
});
it('renders information', () => {
expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual(
'Merge unavailable: merge requests are read-only on archived projects.',

View File

@ -102,74 +102,6 @@ describe('MRWidgetAutoMergeEnabled', () => {
});
describe('computed', () => {
describe('canRemoveSourceBranch', () => {
it('should return true when user is able to remove source branch', () => {
factory({
...defaultMrProps(),
});
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true);
});
it.each`
mergeUserId | currentUserId
${2} | ${1}
${1} | ${2}
`(
'should return false when user id is not the same with who set the MWPS',
({ mergeUserId, currentUserId }) => {
factory({
...defaultMrProps(),
mergeUserId,
currentUserId,
});
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false);
},
);
it('should not find "Delete" button when shouldRemoveSourceBranch set to true', () => {
factory({
...defaultMrProps(),
shouldRemoveSourceBranch: true,
});
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false);
});
it('should find "Delete" button when shouldRemoveSourceBranch overrides state.forceRemoveSourceBranch', () => {
factory(
{
...defaultMrProps(),
shouldRemoveSourceBranch: false,
},
{
forceRemoveSourceBranch: true,
},
);
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true);
});
it('should find "Delete" button when shouldRemoveSourceBranch set to false', () => {
factory({
...defaultMrProps(),
shouldRemoveSourceBranch: false,
});
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true);
});
it('should return false if user is not able to remove the source branch', () => {
factory({
...defaultMrProps(),
canRemoveSourceBranch: false,
});
expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false);
});
});
describe('cancelButtonText', () => {
it('should return "Cancel" if MWPS is selected', () => {
factory({
@ -265,42 +197,6 @@ describe('MRWidgetAutoMergeEnabled', () => {
expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true);
});
it('should show source branch will be deleted text when it source branch set to remove', () => {
factory({
...defaultMrProps(),
shouldRemoveSourceBranch: true,
});
const normalizedText = wrapper.text().replace(/\s+/g, ' ');
expect(normalizedText).toContain('Deletes the source branch');
expect(normalizedText).not.toContain('Does not delete the source branch');
});
it('should not show delete source branch button when user not able to delete source branch', () => {
factory({
...defaultMrProps(),
currentUserId: 4,
});
expect(wrapper.find('.js-remove-source-branch').exists()).toBe(false);
});
it('should disable delete source branch button when the action is in progress', async () => {
factory({
...defaultMrProps(),
});
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
isRemovingSourceBranch: true,
});
await nextTick();
expect(wrapper.find('.js-remove-source-branch').props('loading')).toBe(true);
});
it('should render the status text as "...to merged automatically" if MWPS is selected', () => {
factory({
...defaultMrProps(),

View File

@ -15,10 +15,6 @@ describe('MRWidgetChecking', () => {
vm.$destroy();
});
it('renders disabled button', () => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
});
it('renders loading icon', () => {
expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});

View File

@ -36,28 +36,4 @@ describe('MRWidgetClosed', () => {
it('renders warning icon', () => {
expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
});
it('renders closed by information with author and time', () => {
expect(
vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '),
).toContain('Closed by Administrator less than a minute ago');
});
it('links to the user that closed the MR', () => {
expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual(
'http://localhost:3000/root',
);
});
it('renders information about the changes not being merged', () => {
expect(
vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '),
).toContain('The changes were not merged into so_long_jquery');
});
it('renders link for target branch', () => {
expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual(
'/twitter/flight/commits/so_long_jquery',
);
});
});

View File

@ -1,6 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import MrWidgetFailedToMerge from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
@ -116,7 +115,6 @@ describe('MRWidgetFailedToMerge', () => {
it('renders warning icon and disabled merge button', () => {
expect(wrapper.find('.js-ci-status-icon-warning')).not.toBeNull();
expect(wrapper.find(StatusIcon).props('showDisabledButton')).toBe(true);
});
it('renders given error', () => {

View File

@ -1,5 +1,5 @@
import { getByRole } from '@testing-library/dom';
import Vue, { nextTick } from 'vue';
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
@ -10,14 +10,6 @@ import eventHub from '~/vue_merge_request_widget/event_hub';
describe('MRWidgetMerged', () => {
let vm;
const targetBranch = 'foo';
const selectors = {
get copyMergeShaButton() {
return vm.$el.querySelector('button.js-mr-merged-copy-sha');
},
get mergeCommitShaLink() {
return vm.$el.querySelector('a.js-mr-merged-commit-sha');
},
};
beforeEach(() => {
jest.spyOn(document, 'dispatchEvent');
@ -177,58 +169,11 @@ describe('MRWidgetMerged', () => {
expect(vm.$el.textContent).toContain('Administrator');
});
it('renders branch information', () => {
expect(vm.$el.textContent).toContain('The changes were merged into');
expect(vm.$el.textContent).toContain(targetBranch);
});
it('renders information about branch being deleted', () => {
expect(vm.$el.textContent).toContain('The source branch has been deleted');
});
it('shows revert and cherry-pick buttons', () => {
expect(vm.$el.textContent).toContain('Revert');
expect(vm.$el.textContent).toContain('Cherry-pick');
});
it('shows button to copy commit SHA to clipboard', () => {
expect(selectors.copyMergeShaButton).not.toBe(null);
expect(selectors.copyMergeShaButton.dataset.clipboardText).toBe(vm.mr.mergeCommitSha);
});
it('hides button to copy commit SHA if SHA does not exist', async () => {
vm.mr.mergeCommitSha = null;
await nextTick();
expect(selectors.copyMergeShaButton).toBe(null);
expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with');
});
it('shows merge commit SHA link', () => {
expect(selectors.mergeCommitShaLink).not.toBe(null);
expect(selectors.mergeCommitShaLink.text).toContain(vm.mr.shortMergeCommitSha);
expect(selectors.mergeCommitShaLink.href).toBe(vm.mr.mergeCommitPath);
});
it('should not show source branch deleted text', async () => {
vm.mr.sourceBranchRemoved = false;
await nextTick();
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
});
it('should show source branch deleting text', async () => {
vm.mr.isRemovingSourceBranch = true;
vm.mr.sourceBranchRemoved = false;
await nextTick();
expect(vm.$el.innerText).toContain('The source branch is being deleted');
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
});
it('should use mergedEvent mergedAt as tooltip title', () => {
expect(vm.$el.querySelector('time').getAttribute('title')).toBe('Jan 24, 2018 1:02pm UTC');
});

View File

@ -43,19 +43,6 @@ describe('MRWidgetMerging', () => {
).toContain('Merging!');
});
it('renders branch information', () => {
expect(
wrapper
.find('.mr-info-list')
.text()
.trim()
.replace(/\s\s+/g, ' ')
.replace(/[\r\n]+/g, ' '),
).toEqual('Merges changes into branch');
expect(wrapper.find('a').attributes('href')).toBe('/branch-path');
});
describe('initiateMergePolling', () => {
it('should call simplePoll', () => {
wrapper.vm.initiateMergePolling();

View File

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
describe('PipelineFailed', () => {
@ -9,8 +8,6 @@ describe('PipelineFailed', () => {
wrapper = shallowMount(PipelineFailed);
};
const findStatusIcon = () => wrapper.find(statusIcon);
beforeEach(() => {
createComponent();
});
@ -23,8 +20,4 @@ describe('PipelineFailed', () => {
it('should render error message with a disabled merge button', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('merge button should be disabled', () => {
expect(findStatusIcon().props('showDisabledButton')).toBe(true);
});
});

View File

@ -10,7 +10,6 @@ import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/state
import simplePoll from '~/lib/utils/simple_poll';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue';
@ -60,6 +59,7 @@ const createTestMr = (customConfig) => {
transitionStateMachine: (transition) => eventHub.$emit('StateMachineValueChanged', transition),
translateStateToMachine: () => this.transitionStateMachine(),
state: 'open',
canMerge: true,
};
Object.assign(mr, customConfig.mr);
@ -90,7 +90,7 @@ const createReadyToMergeResponse = (customMr) => {
const createComponent = (
customConfig = {},
mergeRequestWidgetGraphql = false,
restructuredMrWidget = false,
restructuredMrWidget = true,
) => {
wrapper = shallowMount(ReadyToMerge, {
propsData: {
@ -111,7 +111,6 @@ const createComponent = (
};
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
const findCommitEditElements = () => wrapper.findAll(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label');
@ -575,71 +574,9 @@ describe('ReadyToMerge', () => {
});
});
describe('commits count collapsible header', () => {
it('should be rendered when fast-forward is disabled', () => {
createComponent();
expect(findCommitsHeaderElement().exists()).toBeTruthy();
});
describe('when fast-forward is enabled', () => {
it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => {
createComponent({
mr: {
ffOnlyEnabled: true,
enableSquashBeforeMerge: true,
squashIsSelected: true,
commitsCount: 2,
},
});
expect(findCommitsHeaderElement().exists()).toBeTruthy();
});
it('should not be rendered if squash before merge is disabled', () => {
createComponent({
mr: {
ffOnlyEnabled: true,
enableSquashBeforeMerge: false,
squash: true,
commitsCount: 2,
},
});
expect(findCommitsHeaderElement().exists()).toBeFalsy();
});
it('should not be rendered if squash is disabled', () => {
createComponent({
mr: {
ffOnlyEnabled: true,
squash: false,
enableSquashBeforeMerge: true,
commitsCount: 2,
},
});
expect(findCommitsHeaderElement().exists()).toBeFalsy();
});
it('should not be rendered if commits count is 1', () => {
createComponent({
mr: {
ffOnlyEnabled: true,
squash: true,
enableSquashBeforeMerge: true,
commitsCount: 1,
},
});
expect(findCommitsHeaderElement().exists()).toBeFalsy();
});
});
});
describe('commits edit components', () => {
describe('when fast-forward merge is enabled', () => {
it('should not be rendered if squash is disabled', () => {
it('should not be rendered if squash is disabled', async () => {
createComponent({
mr: {
ffOnlyEnabled: true,
@ -678,7 +615,7 @@ describe('ReadyToMerge', () => {
expect(findCommitEditElements().length).toBe(0);
});
it('should have one edit component if squash is enabled and there is more than 1 commit', () => {
it('should have one edit component if squash is enabled and there is more than 1 commit', async () => {
createComponent({
mr: {
ffOnlyEnabled: true,
@ -688,18 +625,14 @@ describe('ReadyToMerge', () => {
},
});
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findCommitEditElements().length).toBe(1);
expect(findFirstCommitEditLabel()).toBe('Squash commit message');
});
});
it('should have one edit component when squash is disabled', () => {
createComponent();
expect(findCommitEditElements().length).toBe(1);
});
it('should have two edit components when squash is enabled and there is more than 1 commit', () => {
it('should have two edit components when squash is enabled and there is more than 1 commit', async () => {
createComponent({
mr: {
commitsCount: 2,
@ -708,6 +641,8 @@ describe('ReadyToMerge', () => {
},
});
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findCommitEditElements().length).toBe(2);
});
@ -737,11 +672,12 @@ describe('ReadyToMerge', () => {
},
});
await nextTick();
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findCommitEditElements().length).toBe(2);
});
it('should have one edit components when squash is enabled and there is 1 commit only', () => {
it('should have one edit components when squash is enabled and there is 1 commit only', async () => {
createComponent({
mr: {
commitsCount: 1,
@ -750,16 +686,12 @@ describe('ReadyToMerge', () => {
},
});
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findCommitEditElements().length).toBe(1);
});
it('should have correct edit merge commit label', () => {
createComponent();
expect(findFirstCommitEditLabel()).toBe('Merge commit message');
});
it('should have correct edit squash commit label', () => {
it('should have correct edit squash commit label', async () => {
createComponent({
mr: {
commitsCount: 2,
@ -768,6 +700,8 @@ describe('ReadyToMerge', () => {
},
});
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findFirstCommitEditLabel()).toBe('Squash commit message');
});
});
@ -779,48 +713,26 @@ describe('ReadyToMerge', () => {
expect(findCommitDropdownElement().exists()).toBeFalsy();
});
it('should be rendered if squash is enabled and there is more than 1 commit', () => {
it('should be rendered if squash is enabled and there is more than 1 commit', async () => {
createComponent({
mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 },
});
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findCommitDropdownElement().exists()).toBeTruthy();
});
});
it('renders a tip including a link to docs on templates', () => {
it('renders a tip including a link to docs on templates', async () => {
createComponent();
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(findTipLink().exists()).toBe(true);
});
});
describe('Merge request project settings', () => {
describe('when the merge commit merge method is enabled', () => {
beforeEach(() => {
createComponent({
mr: { ffOnlyEnabled: false },
});
});
it('should not show fast forward message', () => {
expect(wrapper.find('.mr-fast-forward-message').exists()).toBe(false);
});
});
describe('when the fast-forward merge method is enabled', () => {
beforeEach(() => {
createComponent({
mr: { ffOnlyEnabled: true },
});
});
it('should show fast forward message', () => {
expect(wrapper.find('.mr-fast-forward-message').exists()).toBe(true);
});
});
});
describe('Merge button when pipeline has failed', () => {
beforeEach(() => {
createComponent({
@ -872,6 +784,7 @@ describe('ReadyToMerge', () => {
createDefaultGqlComponent();
await waitForPromises();
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
expect(finderFn()).toBe(initialValue);
});
@ -879,6 +792,7 @@ describe('ReadyToMerge', () => {
it('should have updated value after graphql refetch', async () => {
createDefaultGqlComponent();
await waitForPromises();
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
triggerApprovalUpdated();
await waitForPromises();
@ -889,6 +803,7 @@ describe('ReadyToMerge', () => {
it('should not update if user has touched', async () => {
createDefaultGqlComponent();
await waitForPromises();
await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
const input = wrapper.find(inputId);
input.element.value = USER_COMMIT_MESSAGE;

View File

@ -85,8 +85,6 @@ describe('Wip', () => {
expect(el.innerText).toContain(
"Merge blocked: merge request must be marked as ready. It's still marked as draft.",
);
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(el.querySelector('button').innerText).toContain('Merge');
expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain(
'Mark as ready',
);

View File

@ -20,7 +20,6 @@ import {
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
@ -135,18 +134,6 @@ describe('MrWidgetOptions', () => {
});
});
describe('shouldRenderRelatedLinks', () => {
it('should return false for the initial data', () => {
expect(wrapper.vm.shouldRenderRelatedLinks).toBeFalsy();
});
it('should return true if there is relatedLinks in MR', () => {
Vue.set(wrapper.vm.mr, 'relatedLinks', {});
expect(wrapper.vm.shouldRenderRelatedLinks).toBeTruthy();
});
});
describe('shouldRenderSourceBranchRemovalStatus', () => {
beforeEach(() => {
wrapper.vm.mr.state = 'readyToMerge';
@ -519,61 +506,6 @@ describe('MrWidgetOptions', () => {
});
});
describe('rendering relatedLinks', () => {
beforeEach(() => {
return createComponent({
...mockData,
issues_links: {
closing: `
<a class="close-related-link" href="#">
Close
</a>
`,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders if there are relatedLinks', () => {
expect(wrapper.find('.close-related-link').exists()).toBe(true);
});
it('does not render if state is nothingToMerge', async () => {
wrapper.vm.mr.state = stateKey.nothingToMerge;
await nextTick();
expect(wrapper.find('.close-related-link').exists()).toBe(false);
});
});
describe('rendering source branch removal status', () => {
it('renders when user cannot remove branch and branch should be removed', async () => {
wrapper.vm.mr.canRemoveSourceBranch = false;
wrapper.vm.mr.shouldRemoveSourceBranch = true;
wrapper.vm.mr.state = 'readyToMerge';
await nextTick();
const tooltip = wrapper.find('[data-testid="question-o-icon"]');
expect(wrapper.text()).toContain('Deletes the source branch');
expect(tooltip.attributes('title')).toBe(
'A user with write access to the source branch selected this option',
);
});
it('does not render in merged state', async () => {
wrapper.vm.mr.canRemoveSourceBranch = false;
wrapper.vm.mr.shouldRemoveSourceBranch = true;
wrapper.vm.mr.state = 'merged';
await nextTick();
expect(wrapper.text()).toContain('The source branch has been deleted');
expect(wrapper.text()).not.toContain('Deletes the source branch');
});
});
describe('rendering deployments', () => {
const changes = [
{
@ -1062,7 +994,7 @@ describe('MrWidgetOptions', () => {
await createComponent();
expect(pollRequest).toHaveBeenCalledTimes(6);
expect(pollRequest).toHaveBeenCalledTimes(4);
});
});
@ -1100,14 +1032,14 @@ describe('MrWidgetOptions', () => {
registerExtension(pollingErrorExtension);
await createComponent();
expect(pollRequest).toHaveBeenCalledTimes(6);
expect(pollRequest).toHaveBeenCalledTimes(4);
});
it('captures sentry error and displays error when poll has failed', async () => {
registerExtension(pollingErrorExtension);
await createComponent();
expect(Sentry.captureException).toHaveBeenCalledTimes(5);
expect(Sentry.captureException).toHaveBeenCalled();
expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Fetch error'));
expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
});
@ -1126,7 +1058,7 @@ describe('MrWidgetOptions', () => {
expect(
wrapper.find('[data-testid="widget-extension"] [data-testid="toggle-button"]').exists(),
).toBe(false);
expect(Sentry.captureException).toHaveBeenCalledTimes(5);
expect(Sentry.captureException).toHaveBeenCalled();
expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Fetch error'));
expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
});

View File

@ -25,10 +25,6 @@ describe('getStateKey', () => {
expect(bound()).toEqual('readyToMerge');
context.canMerge = false;
expect(bound()).toEqual('notAllowedToMerge');
context.autoMergeEnabled = true;
context.hasMergeableDiscussionsState = true;
@ -105,22 +101,4 @@ describe('getStateKey', () => {
expect(bound()).toEqual('rebase');
});
it.each`
canMerge | isSHAMismatch | stateKey
${true} | ${true} | ${'shaMismatch'}
${false} | ${true} | ${'notAllowedToMerge'}
${false} | ${false} | ${'notAllowedToMerge'}
`(
'returns $stateKey when canMerge is $canMerge and isSHAMismatch is $isSHAMismatch',
({ canMerge, isSHAMismatch, stateKey }) => {
const bound = getStateKey.bind({
canMerge,
isSHAMismatch,
commitsCount: 2,
});
expect(bound()).toEqual(stateKey);
},
);
});

View File

@ -153,11 +153,6 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
end
it 'logs' do
allow(Gitlab::AppJsonLogger).to receive(:info).with(
hash_including(
"class" => "AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker"
)
)
expect(Gitlab::AppJsonLogger).to receive(:info).with(
message: 'Actor was :ci',
project_id: project.id
@ -750,11 +745,6 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
it { expect { pull_access_check }.not_to raise_error }
it 'logs' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
hash_including(
"class" => "AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker"
)
).once
expect(Gitlab::AppJsonLogger).to receive(:info).with(
message: 'Actor was :ci',
project_id: project.id

View File

@ -36,14 +36,14 @@ RSpec.describe Gitlab::Memory::ReportsDaemon do
message: 'finished',
pid: Process.pid,
worker_id: 'worker_1',
report: 'jemalloc_stats'
perf_report: 'jemalloc_stats'
)).twice
daemon.send(:run_thread)
end
it 'sets real time duration gauge' do
expect(report_duration_counter).to receive(:increment).with({ report: 'jemalloc_stats' }, an_instance_of(Integer))
expect(report_duration_counter).to receive(:increment).with({ report: 'jemalloc_stats' }, an_instance_of(Float))
daemon.send(:run_thread)
end

View File

@ -39,6 +39,13 @@ RSpec.describe IssueEntity do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
describe 'current_user' do
it 'has the exprected permissions' do
expect(subject[:current_user]).to include(:can_create_note, :can_update, :can_set_issue_metadata,
:can_award_emoji)
end
end
context 'when issue got moved' do
let(:public_project) { create(:project, :public) }
let(:member) { create(:user) }

View File

@ -129,6 +129,12 @@ module Ci
let!(:build2_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) }
let!(:build1_project3) { create(:ci_build, :pending, :queued, pipeline: pipeline3) }
it 'picks builds one-by-one' do
expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original
expect(execute(shared_runner)).to eq(build1_project1)
end
context 'when using fair scheduling' do
context 'when all builds are pending' do
it 'prefers projects without builds first' do
@ -739,16 +745,6 @@ module Ci
end
end
context 'when a long queue is created' do
it 'picks builds one-by-one' do
expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original
expect(execute(specific_runner)).to eq(pending_job)
end
include_examples 'handles runner assignment'
end
context 'when using pending builds table' do
include_examples 'handles runner assignment'

View File

@ -34,7 +34,10 @@ RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_
context 'cache clearing' do
it 'clears the cache for older diffs on the merge request' do
expect_any_instance_of(Redis).to receive(:del).once.and_call_original
redis = instance_double(Redis)
expect(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
expect(redis).to receive(:del).once
expect(Rails.cache).to receive(:delete).once.and_call_original
subject.execute

View File

@ -208,6 +208,7 @@ RSpec.configure do |config|
include StubFeatureFlags
include StubSnowplow
include StubMember
if ENV['CI'] || ENV['RETRIES']
# This includes the first try, i.e. tests will be run 4 times before failing.

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
module StubMember
def self.included(base)
GroupMember.prepend(StubbedMember::GroupMember)
ProjectMember.prepend(StubbedMember::ProjectMember)
end
end

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
# Extend the ProjectMember & GroupMember class with the ability to
# to run project_authorizations refresh jobs inline.
# This is needed so that calls like `group.add_member(user)` or `create(:project_member)`
# in the specs can be run without including `:sidekiq_inline` trait.
module StubbedMember
extend ActiveSupport::Concern
module ClearDeduplicationData
private
def clear_deduplication_data!
Gitlab::Redis::Queues.with do |redis|
redis.scan_each(match: '*duplicate*').each do |key|
redis.del(key)
end
end
end
end
module GroupMember
include ClearDeduplicationData
private
def refresh_member_authorized_projects(blocking:)
return super unless blocking
# First, we remove all the keys associated with deduplication from Redis.
# We can't perform a full flush with `Gitlab::Redis::Queues.with(&:flushdb)`
# because that is going to remove other, unrelated enqueued jobs as well,
# and that is going to fail some specs.
clear_deduplication_data!
# then we run `super`, which will enqueue a project authorizations refresh job
super
# then we drain (run) the jobs that were enqueued, but only for the worker class we are interested in.
AuthorizedProjectsWorker.drain
ensure
clear_deduplication_data!
end
end
module ProjectMember
include ClearDeduplicationData
private
def refresh_member_authorized_projects(blocking:)
return super unless blocking
clear_deduplication_data!
super
AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.drain
ensure
clear_deduplication_data!
end
end
end

View File

@ -30,19 +30,33 @@ RSpec.describe WaitableWorker do
describe '.bulk_perform_and_wait' do
context '1 job' do
it 'inlines the job' do
args_list = [[1]]
expect(worker).to receive(:bulk_perform_inline).with(args_list).and_call_original
expect(Gitlab::AppJsonLogger).to(
receive(:info).with(a_hash_including('message' => 'running inline',
'class' => 'Gitlab::Foo::Bar::DummyWorker',
'job_status' => 'running',
'queue' => 'foo_bar_dummy'))
.once)
it 'runs the jobs asynchronously' do
arguments = [[1]]
worker.bulk_perform_and_wait(args_list)
expect(worker).to receive(:bulk_perform_async).with(arguments)
expect(worker.counter).to eq(1)
worker.bulk_perform_and_wait(arguments)
end
context 'when the feature flag `always_async_project_authorizations_refresh` is turned off' do
before do
stub_feature_flags(always_async_project_authorizations_refresh: false)
end
it 'inlines the job' do
args_list = [[1]]
expect(worker).to receive(:bulk_perform_inline).with(args_list).and_call_original
expect(Gitlab::AppJsonLogger).to(
receive(:info).with(a_hash_including('message' => 'running inline',
'class' => 'Gitlab::Foo::Bar::DummyWorker',
'job_status' => 'running',
'queue' => 'foo_bar_dummy'))
.once)
worker.bulk_perform_and_wait(args_list)
expect(worker.counter).to eq(1)
end
end
end