Commit 2c73fd2e authored by Miguel Rincon's avatar Miguel Rincon

Merge branch...

Merge branch '218084-merge-widget-gives-ambiguous-ui-instructions-when-merge-trains-are-enabled' into 'master'

MR widget instructions are unclear when merge trains are enabled

Closes #218084

See merge request gitlab-org/gitlab!38619
parents 9289e85a ed092037
...@@ -15,7 +15,16 @@ import SquashBeforeMerge from './squash_before_merge.vue'; ...@@ -15,7 +15,16 @@ import SquashBeforeMerge from './squash_before_merge.vue';
import CommitsHeader from './commits_header.vue'; import CommitsHeader from './commits_header.vue';
import CommitEdit from './commit_edit.vue'; import CommitEdit from './commit_edit.vue';
import CommitMessageDropdown from './commit_message_dropdown.vue'; import CommitMessageDropdown from './commit_message_dropdown.vue';
import { AUTO_MERGE_STRATEGIES } from '../../constants'; import { AUTO_MERGE_STRATEGIES, DANGER, INFO, WARNING } from '../../constants';
const PIPELINE_RUNNING_STATE = 'running';
const PIPELINE_FAILED_STATE = 'failed';
const PIPELINE_PENDING_STATE = 'pending';
const PIPELINE_SUCCESS_STATE = 'success';
const MERGE_FAILED_STATUS = 'failed';
const MERGE_SUCCESS_STATUS = 'success';
const MERGE_HOOK_VALIDATION_ERROR_STATUS = 'hook_validation_error';
export default { export default {
name: 'ReadyToMerge', name: 'ReadyToMerge',
...@@ -29,6 +38,8 @@ export default { ...@@ -29,6 +38,8 @@ export default {
GlSprintf, GlSprintf,
GlLink, GlLink,
GlDeprecatedButton, GlDeprecatedButton,
MergeTrainHelperText: () =>
import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'),
MergeImmediatelyConfirmationDialog: () => MergeImmediatelyConfirmationDialog: () =>
import( import(
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue' 'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
...@@ -60,35 +71,45 @@ export default { ...@@ -60,35 +71,45 @@ export default {
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr; const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) { if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) {
return 'failed'; return PIPELINE_FAILED_STATE;
} else if (this.isAutoMergeAvailable) { }
return 'pending';
} else if (!pipeline) { if (this.isAutoMergeAvailable) {
return 'success'; return PIPELINE_PENDING_STATE;
} else if (isPipelineFailed) { }
return 'failed';
if (pipeline && isPipelineFailed) {
return PIPELINE_FAILED_STATE;
} }
return 'success'; return PIPELINE_SUCCESS_STATE;
}, },
mergeButtonVariant() { mergeButtonVariant() {
if (this.status === 'failed') { if (this.status === PIPELINE_FAILED_STATE) {
return 'danger'; return DANGER;
} else if (this.status === 'pending') {
return 'info';
} }
return 'success';
if (this.status === PIPELINE_PENDING_STATE) {
return INFO;
}
return PIPELINE_SUCCESS_STATE;
}, },
iconClass() { iconClass() {
if (this.shouldRenderMergeTrainHelperText && !this.mr.preventMerge) {
return PIPELINE_RUNNING_STATE;
}
if ( if (
this.status === 'failed' || this.status === PIPELINE_FAILED_STATE ||
!this.commitMessage.length || !this.commitMessage.length ||
!this.mr.isMergeAllowed || !this.mr.isMergeAllowed ||
this.mr.preventMerge this.mr.preventMerge
) { ) {
return 'warning'; return WARNING;
} }
return 'success';
return PIPELINE_SUCCESS_STATE;
}, },
mergeButtonText() { mergeButtonText() {
if (this.isMergingImmediately) { if (this.isMergingImmediately) {
...@@ -167,11 +188,13 @@ export default { ...@@ -167,11 +188,13 @@ export default {
.merge(options) .merge(options)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error'; const hasError =
data.status === MERGE_FAILED_STATUS ||
data.status === MERGE_HOOK_VALIDATION_ERROR_STATUS;
if (AUTO_MERGE_STRATEGIES.includes(data.status)) { if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') { } else if (data.status === MERGE_SUCCESS_STATUS) {
this.initiateMergePolling(); this.initiateMergePolling();
} else if (hasError) { } else if (hasError) {
eventHub.$emit('FailedToMerge', data.merge_error); eventHub.$emit('FailedToMerge', data.merge_error);
...@@ -269,7 +292,7 @@ export default { ...@@ -269,7 +292,7 @@ export default {
<template> <template>
<div> <div>
<div class="mr-widget-body media"> <div class="mr-widget-body media" :class="{ 'gl-pb-3': shouldRenderMergeTrainHelperText }">
<status-icon :status="iconClass" /> <status-icon :status="iconClass" />
<div class="media-body"> <div class="media-body">
<div class="mr-widget-body-controls media space-children"> <div class="mr-widget-body-controls media space-children">
...@@ -358,6 +381,7 @@ export default { ...@@ -358,6 +381,7 @@ export default {
<div <div
v-if="hasPipelineMustSucceedConflict" v-if="hasPipelineMustSucceedConflict"
class="gl-display-flex gl-align-items-center" class="gl-display-flex gl-align-items-center"
data-testid="pipeline-succeed-conflict"
> >
<gl-sprintf :message="pipelineMustSucceedConflictText" /> <gl-sprintf :message="pipelineMustSucceedConflictText" />
<gl-link <gl-link
...@@ -379,6 +403,13 @@ export default { ...@@ -379,6 +403,13 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<merge-train-helper-text
v-if="shouldRenderMergeTrainHelperText"
:pipeline-id="mr.pipeline.id"
:pipeline-link="mr.pipeline.path"
:merge-train-length="mr.mergeTrainsCount"
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
/>
<template v-if="shouldShowMergeControls"> <template v-if="shouldShowMergeControls">
<div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message"> <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
{{ __('Fast-forward merge without a merge commit') }} {{ __('Fast-forward merge without a merge commit') }}
......
...@@ -3,6 +3,7 @@ import { s__ } from '~/locale'; ...@@ -3,6 +3,7 @@ import { s__ } from '~/locale';
export const SUCCESS = 'success'; export const SUCCESS = 'success';
export const WARNING = 'warning'; export const WARNING = 'warning';
export const DANGER = 'danger'; export const DANGER = 'danger';
export const INFO = 'info';
export const WARNING_MESSAGE_CLASS = 'warning_message'; export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message'; export const DANGER_MESSAGE_CLASS = 'danger_message';
......
<script> <script>
import { escape } from 'lodash'; import { GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink } from '@gitlab/ui'; import { s__ } from '~/locale';
import { s__, sprintf } from '~/locale';
export default { export default {
name: 'MergeTrainHelperText', name: 'MergeTrainHelperText',
components: { components: {
GlLink, GlLink,
GlSprintf,
}, },
props: { props: {
pipelineId: { pipelineId: {
...@@ -27,26 +27,13 @@ export default { ...@@ -27,26 +27,13 @@ export default {
}, },
}, },
computed: { computed: {
message() { helperMessage() {
const text = return this.mergeTrainLength === 0
this.mergeTrainLength === 0
? s__( ? s__(
'mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds.', 'mrWidget|This action will start a merge train when pipeline %{pipelineLink} succeeds.',
) )
: s__( : s__(
'mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds.', 'mrWidget|This action will add the merge request to the merge train when pipeline %{pipelineLink} succeeds.',
);
const sanitizedPipelineLink = escape(this.pipelineLink);
return sprintf(
text,
{
pipelineId: this.pipelineId,
linkStart: `<a class="js-pipeline-link" href="${sanitizedPipelineLink}">`,
linkEnd: '</a>',
},
false,
); );
}, },
}, },
...@@ -54,15 +41,21 @@ export default { ...@@ -54,15 +41,21 @@ export default {
</script> </script>
<template> <template>
<section class="js-merge-train-helper-text mr-widget-help border-top"> <section class="js-merge-train-helper-text gl-px-5 gl-pb-5">
<span v-html="message"></span> <div class="gl-pl-7">
<gl-sprintf :message="helperMessage">
<template #pipelineLink>
<gl-link data-testid="pipeline-link" :href="pipelineLink">#{{ pipelineId }}</gl-link>
</template>
</gl-sprintf>
<gl-link <gl-link
:href="mergeTrainWhenPipelineSucceedsDocsPath" :href="mergeTrainWhenPipelineSucceedsDocsPath"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="js-documentation-link" data-testid="documentation-link"
> >
{{ s__('mrWidget|More information') }} {{ s__('mrWidget|More information') }}
</gl-link> </gl-link>
</div>
</section> </section>
</template> </template>
import { isNumber, isString } from 'lodash';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import { __ } from '~/locale'; import { __ } from '~/locale';
import base from '~/vue_merge_request_widget/mixins/ready_to_merge'; import base from '~/vue_merge_request_widget/mixins/ready_to_merge';
...@@ -48,6 +49,15 @@ export default { ...@@ -48,6 +49,15 @@ export default {
} }
return __('Merge when pipeline succeeds'); return __('Merge when pipeline succeeds');
}, },
shouldRenderMergeTrainHelperText() {
return (
this.mr.pipeline &&
isNumber(this.mr.pipeline.id) &&
isString(this.mr.pipeline.path) &&
this.mr.preferredAutoMergeStrategy === MTWPS_MERGE_STRATEGY &&
!this.mr.autoMergeEnabled
);
},
shouldShowMergeImmediatelyDropdown() { shouldShowMergeImmediatelyDropdown() {
if (this.mr.preferredAutoMergeStrategy === MT_MERGE_STRATEGY) { if (this.mr.preferredAutoMergeStrategy === MT_MERGE_STRATEGY) {
return true; return true;
......
<script> <script>
import { isNumber, isString } from 'lodash';
import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue'; import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue';
import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue'; import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue';
import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin'; import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
...@@ -12,12 +11,9 @@ import { s__, __, sprintf } from '~/locale'; ...@@ -12,12 +11,9 @@ import { s__, __, sprintf } from '~/locale';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue'; import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue'; import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue';
import MergeTrainHelperText from './components/merge_train_helper_text.vue';
import { MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
export default { export default {
components: { components: {
MergeTrainHelperText,
MrWidgetLicenses, MrWidgetLicenses,
MrWidgetGeoSecondaryNode, MrWidgetGeoSecondaryNode,
MrWidgetPolicyViolation, MrWidgetPolicyViolation,
...@@ -156,16 +152,6 @@ export default { ...@@ -156,16 +152,6 @@ export default {
this.loadingLoadPerformanceFailed, this.loadingLoadPerformanceFailed,
); );
}, },
shouldRenderMergeTrainHelperText() {
return (
this.mr.pipeline &&
isNumber(this.mr.pipeline.id) &&
isString(this.mr.pipeline.path) &&
this.mr.preferredAutoMergeStrategy === MTWPS_MERGE_STRATEGY &&
!this.mr.autoMergeEnabled
);
},
licensesApiPath() { licensesApiPath() {
return gl?.mrWidgetData?.license_scanning_comparison_path || null; return gl?.mrWidgetData?.license_scanning_comparison_path || null;
}, },
...@@ -371,7 +357,6 @@ export default { ...@@ -371,7 +357,6 @@ export default {
<div class="mr-widget-section"> <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" /> <component :is="componentName" :mr="mr" :service="service" />
<div class="mr-widget-info"> <div class="mr-widget-info">
<section v-if="mr.allowCollaboration" class="mr-info-list mr-links"> <section v-if="mr.allowCollaboration" class="mr-info-list mr-links">
<p> <p>
...@@ -404,13 +389,6 @@ export default { ...@@ -404,13 +389,6 @@ export default {
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
</div> </div>
</div> </div>
<merge-train-helper-text
v-if="shouldRenderMergeTrainHelperText"
:pipeline-id="mr.pipeline.id"
:pipeline-link="mr.pipeline.path"
:merge-train-length="mr.mergeTrainsCount"
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
/>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div> <div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div> </div>
<mr-widget-pipeline-container <mr-widget-pipeline-container
......
---
title: Update Merge Train helper text
merge_request: 38619
author:
type: changed
...@@ -30,7 +30,7 @@ RSpec.describe 'User adds to merge train when pipeline succeeds', :js do ...@@ -30,7 +30,7 @@ RSpec.describe 'User adds to merge train when pipeline succeeds', :js do
expect(page).to have_button('Start merge train when pipeline succeeds') expect(page).to have_button('Start merge train when pipeline succeeds')
within('.js-merge-train-helper-text') do within('.js-merge-train-helper-text') do
expect(page).to have_content("This merge request will start a merge train when pipeline ##{pipeline.id} succeeds.") expect(page).to have_content("This action will start a merge train when pipeline ##{pipeline.id} succeeds.")
expect(page).to have_link('More information', expect(page).to have_link('More information',
href: MergeRequestPresenter.new(merge_request).merge_train_when_pipeline_succeeds_docs_path) href: MergeRequestPresenter.new(merge_request).merge_train_when_pipeline_succeeds_docs_path)
end end
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue'; import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue';
describe('MergeTrainHelperText', () => { describe('MergeTrainHelperText', () => {
let wrapper; let wrapper;
const factory = propsData => { const defaultProps = {
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
};
const findDocumentationLink = () => wrapper.find('[data-testid="documentation-link"]');
const findPipelineLink = () => wrapper.find('[data-testid="pipeline-link"]');
const createWrapper = propsData => {
wrapper = shallowMount(MergeTrainHelperText, { wrapper = shallowMount(MergeTrainHelperText, {
propsData, propsData: {
...defaultProps,
...propsData,
},
stubs: {
GlSprintf,
GlLink,
},
}); });
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should return the "start" version of the message if there is no existing merge train', () => { it('should return the "start" version of the message if there is no existing merge train', () => {
factory({ createWrapper({ mergeTrainLength: 0 });
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 0,
});
expect(trimText(wrapper.text())).toBe( expect(trimText(wrapper.text())).toBe(
'This merge request will start a merge train when pipeline #123 succeeds. More information', 'This action will start a merge train when pipeline #123 succeeds. More information',
); );
}); });
it('should render the correct pipeline link in the helper text', () => { it('should render the correct pipeline link in the helper text', () => {
factory({ createWrapper();
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const pipelineLink = wrapper.find('.js-pipeline-link').element; const pipelineLink = findPipelineLink();
expect(pipelineLink).toExist(); expect(pipelineLink.exists()).toBe(true);
expect(pipelineLink.textContent).toContain('#123'); expect(pipelineLink.text()).toContain('#123');
expect(pipelineLink).toHaveAttr('href', 'path/to/pipeline'); expect(pipelineLink.attributes('href')).toBe(defaultProps.pipelineLink);
});
it('should sanitize the pipeline link', () => {
factory({
pipelineId: 123,
pipelineLink: '"></a> <script>console.log("hacked!!")</script> <a href="',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const pipelineLink = wrapper.find('.js-pipeline-link').element;
expect(pipelineLink).toExist();
// The escaped characters are un-escaped when rendered by the DOM,
// so we expect the value of the "href" attr to be exactly the same
// as the input. If the link was not sanitized, the "href" attr
// would equal "".
expect(pipelineLink).toHaveAttr(
'href',
'"></a> <script>console.log("hacked!!")</script> <a href="',
);
}); });
it('should render the correct documentation link in the helper text', () => { it('should render the correct documentation link in the helper text', () => {
factory({ createWrapper();
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const docLink = wrapper.find(GlLink); expect(findDocumentationLink().exists()).toBe(true);
expect(findDocumentationLink().attributes('href')).toBe(
expect(docLink.exists()).toBe(true); defaultProps.mergeTrainWhenPipelineSucceedsDocsPath,
expect(docLink.attributes().href).toBe('path/to/help'); );
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { MERGE_DISABLED_TEXT_UNAPPROVED } from 'ee/vue_merge_request_widget/mixins/ready_to_merge'; import { MERGE_DISABLED_TEXT_UNAPPROVED } from 'ee/vue_merge_request_widget/mixins/ready_to_merge';
import MergeImmediatelyConfirmationDialog from 'ee/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'; import MergeImmediatelyConfirmationDialog from 'ee/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue';
import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import { import {
MWPS_MERGE_STRATEGY, MWPS_MERGE_STRATEGY,
...@@ -11,7 +12,7 @@ import { ...@@ -11,7 +12,7 @@ import {
MERGE_DISABLED_TEXT, MERGE_DISABLED_TEXT,
PIPELINE_MUST_SUCCEED_CONFLICT_TEXT, PIPELINE_MUST_SUCCEED_CONFLICT_TEXT,
} from '~/vue_merge_request_widget/mixins/ready_to_merge'; } from '~/vue_merge_request_widget/mixins/ready_to_merge';
import { GlSprintf } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
describe('ReadyToMerge', () => { describe('ReadyToMerge', () => {
let wrapper; let wrapper;
...@@ -22,9 +23,15 @@ describe('ReadyToMerge', () => { ...@@ -22,9 +23,15 @@ describe('ReadyToMerge', () => {
poll: () => {}, poll: () => {},
}; };
const activePipeline = {
id: 1,
path: 'path/to/pipeline',
active: true,
};
const mr = { const mr = {
isPipelineActive: false, isPipelineActive: false,
pipeline: null, pipeline: { id: 1, path: 'path/to/pipeline' },
isPipelineFailed: false, isPipelineFailed: false,
isPipelinePassing: false, isPipelinePassing: false,
isMergeAllowed: true, isMergeAllowed: true,
...@@ -46,6 +53,8 @@ describe('ReadyToMerge', () => { ...@@ -46,6 +53,8 @@ describe('ReadyToMerge', () => {
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs', mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs',
mergeTrainWhenPipelineSucceedsDocsPath: '/merge-train/docs',
mergeTrainsCount: 0,
}; };
const factory = (mrUpdates = {}) => { const factory = (mrUpdates = {}) => {
...@@ -56,6 +65,9 @@ describe('ReadyToMerge', () => { ...@@ -56,6 +65,9 @@ describe('ReadyToMerge', () => {
}, },
stubs: { stubs: {
MergeImmediatelyConfirmationDialog, MergeImmediatelyConfirmationDialog,
MergeTrainHelperText,
GlSprintf,
GlLink,
}, },
}); });
...@@ -63,12 +75,22 @@ describe('ReadyToMerge', () => { ...@@ -63,12 +75,22 @@ describe('ReadyToMerge', () => {
}; };
const findResolveItemsMessage = () => wrapper.find(GlSprintf); const findResolveItemsMessage = () => wrapper.find(GlSprintf);
const findPipelineConflictMessage = () =>
wrapper.find('[data-testid="pipeline-succeed-conflict"]');
const findMergeButton = () => wrapper.find('.qa-merge-button'); const findMergeButton = () => wrapper.find('.qa-merge-button');
const findMergeButtonDropdown = () => wrapper.find('.js-merge-moment'); const findMergeButtonDropdown = () => wrapper.find('.js-merge-moment');
const findMergeImmediatelyButton = () => wrapper.find('.js-merge-immediately-button'); const findMergeImmediatelyButton = () => wrapper.find('.js-merge-immediately-button');
const findMergeTrainHelperText = () => wrapper.find(MergeTrainHelperText);
const findMergeTrainPipelineLink = () =>
findMergeTrainHelperText().find('[data-testid="pipeline-link"]');
const findMergeTrainDocumentationLink = () =>
findMergeTrainHelperText().find('[data-testid="documentation-link"]');
afterEach(() => { afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}
}); });
describe('computed', () => { describe('computed', () => {
...@@ -178,6 +200,87 @@ describe('ReadyToMerge', () => { ...@@ -178,6 +200,87 @@ describe('ReadyToMerge', () => {
}); });
}); });
describe('shouldRenderMergeTrainHelperText', () => {
it('should render the helper text if MTWPS is available and the user has not yet pressed the MTWPS button', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
});
expect(findMergeTrainHelperText().exists()).toBe(true);
});
});
describe('merge train helper text', () => {
it('does not render the merge train helper text if the MTWPS strategy is not available', () => {
factory({
availableAutoMergeStrategies: [MT_MERGE_STRATEGY],
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().exists()).toBe(false);
});
it('renders the correct merge train helper text when there is an existing merge train', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 2,
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().text()).toContain(
`This action will add the merge request to the merge train when pipeline #${activePipeline.id} succeeds.`,
);
});
it('renders the correct merge train helper text when there is no existing merge train', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().text()).toContain(
`This action will start a merge train when pipeline #${activePipeline.id} succeeds.`,
);
});
it('renders the correct pipeline link inside the message', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
const pipelineLink = findMergeTrainPipelineLink();
expect(pipelineLink.text()).toContain(activePipeline.id);
expect(pipelineLink.attributes('href')).toBe(activePipeline.path);
});
it('renders the documentation link inside the message', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
const pipelineLink = findMergeTrainDocumentationLink();
expect(pipelineLink.text()).toContain('More information');
expect(pipelineLink.attributes('href')).toBe(mr.mergeTrainWhenPipelineSucceedsDocsPath);
});
});
describe('shouldShowMergeImmediatelyDropdown', () => { describe('shouldShowMergeImmediatelyDropdown', () => {
it('should return false if no pipeline is active', () => { it('should return false if no pipeline is active', () => {
factory({ factory({
...@@ -275,7 +378,7 @@ describe('ReadyToMerge', () => { ...@@ -275,7 +378,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show cannot merge text', () => { it('should show cannot merge text', () => {
expect(findResolveItemsMessage().attributes('message')).toBe(MERGE_DISABLED_TEXT); expect(findResolveItemsMessage().text()).toBe(MERGE_DISABLED_TEXT);
}); });
it('should show disabled merge button', () => { it('should show disabled merge button', () => {
...@@ -298,7 +401,7 @@ describe('ReadyToMerge', () => { ...@@ -298,7 +401,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show approvals needed text', () => { it('should show approvals needed text', () => {
expect(findResolveItemsMessage().attributes('message')).toBe(MERGE_DISABLED_TEXT_UNAPPROVED); expect(findResolveItemsMessage().text()).toBe(MERGE_DISABLED_TEXT_UNAPPROVED);
}); });
}); });
...@@ -313,9 +416,7 @@ describe('ReadyToMerge', () => { ...@@ -313,9 +416,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show a custom message that explains the conflict', () => { it('should show a custom message that explains the conflict', () => {
expect(findResolveItemsMessage().attributes('message')).toBe( expect(findPipelineConflictMessage().text()).toBe(PIPELINE_MUST_SUCCEED_CONFLICT_TEXT);
PIPELINE_MUST_SUCCEED_CONFLICT_TEXT,
);
}); });
}); });
}); });
...@@ -16,7 +16,6 @@ import mockData, { ...@@ -16,7 +16,6 @@ import mockData, {
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants'; import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import { import {
sastDiffSuccessMock, sastDiffSuccessMock,
dastDiffSuccessMock, dastDiffSuccessMock,
...@@ -942,20 +941,6 @@ describe('ee merge request widget options', () => { ...@@ -942,20 +941,6 @@ describe('ee merge request widget options', () => {
expect(vm.shouldRenderApprovals).toBeTruthy(); expect(vm.shouldRenderApprovals).toBeTruthy();
}); });
}); });
describe('shouldRenderMergeTrainHelperText', () => {
it('should return true if MTWPS is available and the user has not yet pressed the MTWPS button', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
auto_merge_enabled: false,
},
});
expect(vm.shouldRenderMergeTrainHelperText).toBe(true);
});
});
}); });
describe('rendering source branch removal status', () => { describe('rendering source branch removal status', () => {
...@@ -1054,115 +1039,6 @@ describe('ee merge request widget options', () => { ...@@ -1054,115 +1039,6 @@ describe('ee merge request widget options', () => {
}); });
}); });
describe('merge train helper text', () => {
const getHelperTextElement = () => vm.$el.querySelector('.js-merge-train-helper-text');
it('does not render the merge train helpe text if the MTWPS strategy is not available', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MT_MERGE_STRATEGY],
pipeline: {
...mockData.pipeline,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).not.toExist();
});
it('renders the correct merge train helper text when there is an existing merge train', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_trains_count: 2,
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).toExist();
expect(helperText.textContent).toContain(
'This merge request will be added to the merge train when pipeline #123 succeeds.',
);
});
it('renders the correct merge train helper text when there is no existing merge train', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_trains_count: 0,
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).toExist();
expect(helperText.textContent).toContain(
'This merge request will start a merge train when pipeline #123 succeeds.',
);
});
it('renders the correct pipeline link inside the message', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
path: 'path/to/pipeline',
active: true,
},
},
});
const pipelineLink = getHelperTextElement().querySelector('.js-pipeline-link');
expect(pipelineLink).toExist();
expect(pipelineLink.textContent).toContain('#123');
expect(pipelineLink).toHaveAttr('href', 'path/to/pipeline');
});
it('renders the documentation link inside the message', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
active: true,
},
},
});
const pipelineLink = getHelperTextElement().querySelector('.js-documentation-link');
expect(pipelineLink).toExist();
expect(pipelineLink.textContent).toContain('More information');
expect(pipelineLink).toHaveAttr('href', 'path/to/help');
});
});
describe('data', () => { describe('data', () => {
it('passes approval api paths to service', () => { it('passes approval api paths to service', () => {
const paths = { const paths = {
......
...@@ -29333,19 +29333,19 @@ msgstr "" ...@@ -29333,19 +29333,19 @@ msgstr ""
msgid "mrWidget|There are merge conflicts" msgid "mrWidget|There are merge conflicts"
msgstr "" msgstr ""
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected." msgid "mrWidget|This action will add the merge request to the merge train when pipeline %{pipelineLink} succeeds."
msgstr "" msgstr ""
msgid "mrWidget|This merge request failed to be merged automatically" msgid "mrWidget|This action will start a merge train when pipeline %{pipelineLink} succeeds."
msgstr "" msgstr ""
msgid "mrWidget|This merge request is in the process of being merged" msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
msgstr "" msgstr ""
msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds." msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "" msgstr ""
msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds." msgid "mrWidget|This merge request is in the process of being merged"
msgstr "" msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled" msgid "mrWidget|This project is archived, write access has been disabled"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment