Commit 74251950 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ee-9186-implement-atmtwps-state-to-mr-widget' into 'master'

Update the merge request widget's "Merge" button to support merge trains

See merge request gitlab-org/gitlab-ee!12156
parents 9e5d60e3 72bddd7c
<script>
import _ from 'underscore';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
export default {
name: 'MRWidgetMergeWhenPipelineSucceeds',
name: 'MRWidgetAutoMergeEnabled',
components: {
MrWidgetAuthor,
statusIcon,
},
mixins: [autoMergeMixin],
props: {
mr: {
type: Object,
......@@ -57,7 +61,7 @@ export default {
removeSourceBranch() {
const options = {
sha: this.mr.sha,
auto_merge_strategy: 'merge_when_pipeline_succeeds',
auto_merge_strategy: this.mr.autoMergeStrategy,
should_remove_source_branch: true,
};
......@@ -66,7 +70,7 @@ export default {
.merge(options)
.then(res => res.data)
.then(data => {
if (data.status === 'merge_when_pipeline_succeeds') {
if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
......@@ -84,9 +88,9 @@ export default {
<div class="media-body">
<h4 class="d-flex align-items-start">
<span class="append-right-10">
{{ s__('mrWidget|Set by') }}
<span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span>
<mr-widget-author :author="mr.setToAutoMergeBy" />
{{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }}
<span class="js-status-text-after-author">{{ statusTextAfterAuthor }}</span>
</span>
<a
v-if="mr.canCancelAutomaticMerge"
......@@ -97,7 +101,7 @@ export default {
@click.prevent="cancelAutomaticMerge"
>
<i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
{{ s__('mrWidget|Cancel automatic merge') }}
{{ cancelButtonText }}
</a>
</h4>
<section class="mr-info-list">
......
<script>
import _ from 'underscore';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
......@@ -12,6 +13,7 @@ import SquashBeforeMerge from './squash_before_merge.vue';
import CommitsHeader from './commits_header.vue';
import CommitEdit from './commit_edit.vue';
import CommitMessageDropdown from './commit_message_dropdown.vue';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
export default {
name: 'ReadyToMerge',
......@@ -30,8 +32,6 @@ export default {
data() {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
autoMergeStrategy: undefined,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
......@@ -42,18 +42,18 @@ export default {
};
},
computed: {
shouldShowAutoMergeText() {
return this.mr.isPipelineActive;
isAutoMergeAvailable() {
return !_.isEmpty(this.mr.availableAutoMergeStrategies);
},
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) {
return 'failed';
} else if (this.isAutoMergeAvailable) {
return 'pending';
} else if (!pipeline) {
return 'success';
} else if (isPipelineActive) {
return 'pending';
} else if (isPipelineFailed) {
return 'failed';
}
......@@ -87,14 +87,14 @@ export default {
mergeButtonText() {
if (this.isMergingImmediately) {
return __('Merge in progress');
} else if (this.shouldShowAutoMergeText) {
return __('Merge when pipeline succeeds');
} else if (this.isAutoMergeAvailable) {
return this.autoMergeText;
}
return 'Merge';
return __('Merge');
},
shouldShowMergeOptionsDropdown() {
return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
return this.isAutoMergeAvailable && !this.mr.onlyAllowMergeIfPipelineSucceeds;
},
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
......@@ -104,7 +104,7 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
return this.mr.isMergeAllowed || this.isAutoMergeAvailable;
},
shouldShowSquashEdit() {
return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
......@@ -118,20 +118,15 @@ export default {
const { commitMessageWithDescription, commitMessage } = this.mr;
this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
// TODO: Remove no-param-reassign
if (mergeWhenBuildSucceeds === undefined) {
mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
} else if (mergeImmediately) {
handleMergeButtonClick(useAutoMerge, mergeImmediately = false) {
if (mergeImmediately) {
this.isMergingImmediately = true;
}
this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;
const options = {
sha: this.mr.sha,
commit_message: this.commitMessage,
auto_merge_strategy: this.autoMergeStrategy,
auto_merge_strategy: useAutoMerge ? this.mr.preferredAutoMergeStrategy : undefined,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
squash_commit_message: this.squashCommitMessage,
......@@ -144,7 +139,7 @@ export default {
.then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
if (data.status === 'merge_when_pipeline_succeeds') {
if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') {
this.initiateMergePolling();
......@@ -242,13 +237,13 @@ export default {
:class="mergeButtonClass"
type="button"
class="qa-merge-button"
@click="handleMergeButtonClick()"
@click="handleMergeButtonClick(isAutoMergeAvailable)"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
</button>
<button
v-if="shouldShowMergeOptionsDropdown"
v-if="isAutoMergeAvailable"
:disabled="isMergeButtonDisabled"
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
......@@ -264,15 +259,13 @@ export default {
>
<li>
<a
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
class="auto_merge_enabled qa-merge-when-pipeline-succeeds-option"
href="#"
@click.prevent="handleMergeButtonClick(true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
<span class="media-body merge-opt-title">{{
__('Merge when pipeline succeeds')
}}</span>
<span class="media-body merge-opt-title">{{ autoMergeText }}</span>
</span>
</a>
</li>
......
......@@ -3,3 +3,13 @@ export const DANGER = 'danger';
export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message';
export const MWPS_MERGE_STRATEGY = 'merge_when_pipeline_succeeds';
export const ATMTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
export const MT_MERGE_STRATEGY = 'merge_train';
export const AUTO_MERGE_STRATEGIES = [
MWPS_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY,
];
import { s__ } from '~/locale';
export default {
computed: {
statusTextBeforeAuthor() {
return s__('mrWidget|Set by');
},
statusTextAfterAuthor() {
return s__('mrWidget|to be merged automatically when the pipeline succeeds');
},
cancelButtonText() {
return s__('mrWidget|Cancel automatic merge');
},
},
};
import { __ } from '~/locale';
export default {
computed: {
isMergeButtonDisabled() {
......@@ -9,5 +11,9 @@ export default {
this.mr.preventMerge,
);
},
autoMergeText() {
// MWPS is currently the only auto merge strategy available in CE
return __('Merge when pipeline succeeds');
},
},
};
......@@ -29,7 +29,7 @@ import UnresolvedDiscussionsState from './components/states/unresolved_discussio
import PipelineBlockedState from './components/states/mr_widget_pipeline_blocked.vue';
import PipelineFailedState from './components/states/pipeline_failed.vue';
import FailedToMerge from './components/states/mr_widget_failed_to_merge.vue';
import MergeWhenPipelineSucceedsState from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import MrWidgetAutoMergeEnabled from './components/states/mr_widget_auto_merge_enabled.vue';
import AutoMergeFailed from './components/states/mr_widget_auto_merge_failed.vue';
import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub';
......@@ -64,7 +64,7 @@ export default {
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
'mr-widget-pipeline-failed': PipelineFailedState,
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
MrWidgetAutoMergeEnabled,
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
......
import Timeago from 'timeago.js';
import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
import { ATMTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
export default class MergeRequestStore {
constructor(data) {
......@@ -77,6 +79,10 @@ export default class MergeRequestStore {
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.autoMergeEnabled = Boolean(data.auto_merge_enabled);
this.autoMergeStrategy = data.auto_merge_strategy;
this.availableAutoMergeStrategies = data.available_auto_merge_strategies;
this.preferredAutoMergeStrategy = MergeRequestStore.getPreferredAutoMergeStrategy(
this.availableAutoMergeStrategies,
);
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
......@@ -104,7 +110,9 @@ export default class MergeRequestStore {
this.sourceProjectFullPath = data.source_project_full_path;
this.sourceProjectId = data.source_project_id;
this.targetProjectId = data.target_project_id;
this.mergePipelinesEnabled = data.merge_pipelines_enabled;
this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled);
this.mergeTrainsCount = data.merge_trains_count || 0;
this.mergeTrainIndex = data.merge_train_index;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......@@ -204,4 +212,16 @@ export default class MergeRequestStore {
return timeagoInstance.format(date);
}
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
if (_.includes(availableAutoMergeStrategies, ATMTWPS_MERGE_STRATEGY)) {
return ATMTWPS_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MT_MERGE_STRATEGY)) {
return MT_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) {
return MWPS_MERGE_STRATEGY;
}
return undefined;
}
}
......@@ -13,7 +13,7 @@ const stateToComponentMap = {
unresolvedDiscussions: 'mr-widget-unresolved-discussions',
pipelineBlocked: 'mr-widget-pipeline-blocked',
pipelineFailed: 'mr-widget-pipeline-failed',
autoMergeEnabled: 'mr-widget-merge-when-pipeline-succeeds',
autoMergeEnabled: 'mr-widget-auto-merge-enabled',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'sha-mismatch',
......
---
title: Update the merge request widget's "Merge" button to support merge trains
merge_request: 27594
author:
type: added
import {
MT_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
MWPS_MERGE_STRATEGY,
} from '~/vue_merge_request_widget/constants';
import { s__ } from '~/locale';
export default {
computed: {
statusTextBeforeAuthor() {
if (this.mr.autoMergeStrategy === MT_MERGE_STRATEGY) {
return s__('mrWidget|Added to the merge train by');
}
return s__('mrWidget|Set by');
},
statusTextAfterAuthor() {
if (this.mr.autoMergeStrategy === ATMTWPS_MERGE_STRATEGY && this.mr.mergeTrainsCount === 0) {
return s__('mrWidget|to start a merge train when the pipeline succeeds');
} else if (
this.mr.autoMergeStrategy === ATMTWPS_MERGE_STRATEGY &&
this.mr.mergeTrainsCount !== 0
) {
return s__('mrWidget|to be added to the merge train when the pipeline succeeds');
} else if (this.mr.autoMergeStrategy === MWPS_MERGE_STRATEGY) {
return s__('mrWidget|to be merged automatically when the pipeline succeeds');
}
return '';
},
cancelButtonText() {
if (this.mr.autoMergeStrategy === MT_MERGE_STRATEGY) {
return s__('mrWidget|Remove from merge train');
}
return s__('mrWidget|Cancel automatic merge');
},
},
};
import { ATMTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import { __ } from '~/locale';
export default {
computed: {
isApprovalNeeded() {
......@@ -13,5 +16,19 @@ export default {
this.mr.preventMerge,
);
},
autoMergeText() {
if (this.mr.preferredAutoMergeStrategy === ATMTWPS_MERGE_STRATEGY) {
if (this.mr.mergeTrainsCount === 0) {
return __('Start merge train when pipeline succeeds');
}
return __('Add to merge train when pipeline succeeds');
} else if (this.mr.preferredAutoMergeStrategy === MT_MERGE_STRATEGY) {
if (this.mr.mergeTrainsCount === 0) {
return __('Start merge train');
}
return __('Add to merge train');
}
return __('Merge when pipeline succeeds');
},
},
};
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { trimText } from 'spec/helpers/text_helper';
import MRWidgetAutoMergeEnabled from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
import {
MWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
} from '~/vue_merge_request_widget/constants';
describe('MRWidgetAutoMergeEnabled', () => {
const localVue = createLocalVue();
let wrapper;
let vm;
const service = {
merge: () => {},
poll: () => {},
};
const mr = {
shouldRemoveSourceBranch: false,
canRemoveSourceBranch: true,
canCancelAutomaticMerge: true,
mergeUserId: 1,
currentUserId: 1,
setToAutoMergeBy: {},
sha: '1EA2EZ34',
targetBranchPath: '/foo/bar',
targetBranch: 'foo',
autoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
};
const factory = (mrUpdates = {}) => {
wrapper = shallowMount(localVue.extend(MRWidgetAutoMergeEnabled), {
propsData: {
mr: { ...mr, ...mrUpdates },
service,
},
localVue,
sync: false,
});
({ vm } = wrapper);
};
afterEach(() => {
wrapper.destroy();
});
describe('computed', () => {
describe('statusTextBeforeAuthor', () => {
it('should return "Added to the merge train by" if the pipeline has been added to the merge train', () => {
factory({ autoMergeStrategy: MT_MERGE_STRATEGY });
expect(vm.statusTextBeforeAuthor).toBe('Added to the merge train by');
});
it('should return "Set by" if the ATMTWPS is selected', () => {
factory({ autoMergeStrategy: ATMTWPS_MERGE_STRATEGY });
expect(vm.statusTextBeforeAuthor).toBe('Set by');
});
it('should return "Set by" if the MWPS is selected', () => {
factory({ autoMergeStrategy: MWPS_MERGE_STRATEGY });
expect(vm.statusTextBeforeAuthor).toBe('Set by');
});
});
describe('statusTextAfterAuthor', () => {
it('should return "to start a merge train..." if ATMTWPS is selected and there is no existing merge train', () => {
factory({
autoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 0,
});
expect(vm.statusTextAfterAuthor).toBe('to start a merge train when the pipeline succeeds');
});
it('should return "to be added to the merge train..." if ATMTWPS is selected and there is an existing merge train', () => {
factory({
autoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 1,
});
expect(vm.statusTextAfterAuthor).toBe(
'to be added to the merge train when the pipeline succeeds',
);
});
it('should return "to be merged automatically..." if MWPS is selected', () => {
factory({ autoMergeStrategy: MWPS_MERGE_STRATEGY });
expect(vm.statusTextAfterAuthor).toBe(
'to be merged automatically when the pipeline succeeds',
);
});
});
describe('cancelButtonText', () => {
it('should return "Cancel start merge train" if ATMTWPS is selected', () => {
factory({ autoMergeStrategy: ATMTWPS_MERGE_STRATEGY });
expect(vm.cancelButtonText).toBe('Cancel automatic merge');
});
it('should return "Remove from merge train" if the pipeline has been added to the merge train', () => {
factory({ autoMergeStrategy: MT_MERGE_STRATEGY });
expect(vm.cancelButtonText).toBe('Remove from merge train');
});
it('should return "Cancel automatic merge" if MWPS is selected', () => {
factory({ autoMergeStrategy: MWPS_MERGE_STRATEGY });
expect(vm.cancelButtonText).toBe('Cancel automatic merge');
});
});
});
describe('template', () => {
it('should render the status text as "...to start a merge train" if ATMTWPS is selected and there is no existing merge train', () => {
factory({
autoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 0,
});
const statusText = trimText(vm.$el.querySelector('.js-status-text-after-author').innerText);
expect(statusText).toBe('to start a merge train when the pipeline succeeds');
});
it('should render the status text as "...to be added to the merge train" ATMTWPS is selected and there is an existing merge train', () => {
factory({
autoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 1,
});
const statusText = trimText(vm.$el.querySelector('.js-status-text-after-author').innerText);
expect(statusText).toBe('to be added to the merge train when the pipeline succeeds');
});
it('should render the cancel button as "Cancel automatic merge" if ATMTWPS is selected', () => {
factory({ autoMergeStrategy: ATMTWPS_MERGE_STRATEGY });
const cancelButtonText = trimText(vm.$el.querySelector('.js-cancel-auto-merge').innerText);
expect(cancelButtonText).toBe('Cancel automatic merge');
});
});
it('should render the cancel button as "Remove from merge train" if the pipeline has been added to the merge train', () => {
factory({ autoMergeStrategy: MT_MERGE_STRATEGY });
const cancelButtonText = trimText(vm.$el.querySelector('.js-cancel-auto-merge').innerText);
expect(cancelButtonText).toBe('Remove from merge train');
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import {
MWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
} from '~/vue_merge_request_widget/constants';
describe('ReadyToMerge', () => {
const localVue = createLocalVue();
let wrapper;
let vm;
const service = {
merge: () => {},
poll: () => {},
};
const mr = {
isPipelineActive: false,
pipeline: null,
isPipelineFailed: false,
isPipelinePassing: false,
isMergeAllowed: true,
onlyAllowMergeIfPipelineSucceeds: false,
ffOnlyEnabled: false,
hasCI: false,
ciStatus: null,
sha: '12345678',
squash: false,
commitMessage: 'This is the commit message',
squashCommitMessage: 'This is the squash commit message',
commitMessageWithDescription: 'This is the commit message description',
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
targetBranch: 'master',
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
};
const factory = (mrUpdates = {}) => {
wrapper = shallowMount(localVue.extend(ReadyToMerge), {
propsData: {
mr: { ...mr, ...mrUpdates },
service,
},
localVue,
sync: false,
});
({ vm } = wrapper);
};
afterEach(() => {
wrapper.destroy();
});
describe('computed', () => {
describe('mergeButtonText', () => {
it('should return "Merge" when no auto merge strategies are available', () => {
factory({ availableAutoMergeStrategies: [] });
expect(vm.mergeButtonText).toEqual('Merge');
});
it('should return "Merge in progress"', () => {
factory();
localVue.set(vm, 'isMergingImmediately', true);
expect(vm.mergeButtonText).toEqual('Merge in progress');
});
it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => {
factory({
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
});
expect(vm.mergeButtonText).toEqual('Merge when pipeline succeeds');
});
it('should return "Start merge train" when the merge train auto merge stategy is available and there is no existing merge train', () => {
factory({
preferredAutoMergeStrategy: MT_MERGE_STRATEGY,
mergeTrainsCount: 0,
});
expect(vm.mergeButtonText).toEqual('Start merge train');
});
it('should return "Add to merge train" when the merge train auto merge stategy is available and a merge train already exists', () => {
factory({
preferredAutoMergeStrategy: MT_MERGE_STRATEGY,
mergeTrainsCount: 1,
});
expect(vm.mergeButtonText).toEqual('Add to merge train');
});
it('should return "Start merge train when pipeline succeeds" when the ATMTWPS auto merge strategy is available and there is no existing merge train', () => {
factory({
preferredAutoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 0,
});
expect(vm.mergeButtonText).toEqual('Start merge train when pipeline succeeds');
});
it('should return "Add to merge train when pipeline succeeds" when the ATMTWPS auto merge strategy is available and a merge train already exists', () => {
factory({
preferredAutoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 1,
});
expect(vm.mergeButtonText).toEqual('Add to merge train when pipeline succeeds');
});
});
describe('autoMergeText', () => {
it('should return Merge when pipeline succeeds', () => {
factory({ preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY });
expect(vm.autoMergeText).toEqual('Merge when pipeline succeeds');
});
it('should return Start merge train when pipeline succeeds', () => {
factory({
preferredAutoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 0,
});
expect(vm.autoMergeText).toEqual('Start merge train when pipeline succeeds');
});
it('should return Add to merge train when pipeline succeeds', () => {
factory({
preferredAutoMergeStrategy: ATMTWPS_MERGE_STRATEGY,
mergeTrainsCount: 1,
});
expect(vm.autoMergeText).toEqual('Add to merge train when pipeline succeeds');
});
});
});
});
......@@ -754,6 +754,12 @@ msgstr ""
msgid "Add reaction"
msgstr ""
msgid "Add to merge train"
msgstr ""
msgid "Add to merge train when pipeline succeeds"
msgstr ""
msgid "Add to project"
msgstr ""
......@@ -8077,6 +8083,9 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
msgid "Merge"
msgstr ""
msgid "Merge Request"
msgstr ""
......@@ -12483,6 +12492,12 @@ msgstr ""
msgid "Start discussion & reopen %{noteable_name}"
msgstr ""
msgid "Start merge train"
msgstr ""
msgid "Start merge train when pipeline succeeds"
msgstr ""
msgid "Start the Runner!"
msgstr ""
......@@ -16257,6 +16272,9 @@ msgstr ""
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
msgstr ""
msgid "mrWidget|Added to the merge train by"
msgstr ""
msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr ""
......@@ -16386,6 +16404,9 @@ msgstr ""
msgid "mrWidget|Refreshing now"
msgstr ""
msgid "mrWidget|Remove from merge train"
msgstr ""
msgid "mrWidget|Request to merge"
msgstr ""
......@@ -16476,9 +16497,15 @@ msgstr ""
msgid "mrWidget|into"
msgstr ""
msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
msgstr ""
msgid "mrWidget|to be merged automatically when the pipeline succeeds"
msgstr ""
msgid "mrWidget|to start a merge train when the pipeline succeeds"
msgstr ""
msgid "must be greater than start date"
msgstr ""
......
import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { trimText } from 'spec/helpers/text_helper';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('MRWidgetAutoMergeEnabled', () => {
let vm;
const targetBranchPath = '/foo/bar';
const targetBranch = 'foo';
const sha = '1EA2EZ34';
beforeEach(() => {
const Component = Vue.extend(mwpsComponent);
const Component = Vue.extend(autoMergeEnabledComponent);
spyOn(eventHub, '$emit');
vm = mountComponent(Component, {
......@@ -25,6 +27,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
sha,
targetBranchPath,
targetBranch,
autoMergeStrategy: MWPS_MERGE_STRATEGY,
},
service: new MRWidgetService({}),
});
......@@ -66,6 +69,32 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(vm.canRemoveSourceBranch).toBeFalsy();
});
});
describe('statusTextBeforeAuthor', () => {
it('should return "Set by" if the MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.statusTextBeforeAuthor).toBe('Set by');
});
});
describe('statusTextAfterAuthor', () => {
it('should return "to be merged automatically..." if MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.statusTextAfterAuthor).toBe(
'to be merged automatically when the pipeline succeeds',
);
});
});
describe('cancelButtonText', () => {
it('should return "Cancel automatic merge" if MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.cancelButtonText).toBe('Cancel automatic merge');
});
});
});
describe('methods', () => {
......@@ -96,7 +125,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
spyOn(vm.service, 'merge').and.returnValue(
Promise.resolve({
data: {
status: 'merge_when_pipeline_succeeds',
status: MWPS_MERGE_STRATEGY,
},
}),
);
......@@ -106,7 +135,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.merge).toHaveBeenCalledWith({
sha,
auto_merge_strategy: 'merge_when_pipeline_succeeds',
auto_merge_strategy: MWPS_MERGE_STRATEGY,
should_remove_source_branch: true,
});
done();
......@@ -119,6 +148,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.innerText).toContain('to be merged automatically when the pipeline succeeds');
expect(vm.$el.innerText).toContain('The changes will be merged into');
expect(vm.$el.innerText).toContain(targetBranch);
expect(vm.$el.innerText).toContain('The source branch will not be deleted');
......@@ -174,5 +204,27 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
done();
});
});
it('should render the status text as "...to merged automatically" if MWPS is selected', done => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
Vue.nextTick(() => {
const statusText = trimText(vm.$el.querySelector('.js-status-text-after-author').innerText);
expect(statusText).toBe('to be merged automatically when the pipeline succeeds');
done();
});
});
it('should render the cancel button as "Cancel automatic merge" if MWPS is selected', done => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
Vue.nextTick(() => {
const cancelButtonText = trimText(vm.$el.querySelector('.js-cancel-auto-merge').innerText);
expect(cancelButtonText).toBe('Cancel automatic merge');
done();
});
});
});
});
......@@ -6,6 +6,7 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { MWPS_MERGE_STRATEGY, ATMTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
const commitMessage = 'This is the commit message';
const squashCommitMessage = 'This is the squash commit message';
......@@ -29,6 +30,8 @@ const createTestMr = customConfig => {
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
targetBranch: 'master',
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
};
Object.assign(mr, customConfig.mr);
......@@ -80,7 +83,6 @@ describe('ReadyToMerge', () => {
it('should have default data', () => {
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.showCommitMessageEditor).toBeFalsy();
expect(vm.isMakingRequest).toBeFalsy();
expect(vm.isMergingImmediately).toBeFalsy();
......@@ -91,47 +93,51 @@ describe('ReadyToMerge', () => {
});
describe('computed', () => {
describe('shouldShowAutoMergeText', () => {
it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true;
describe('isAutoMergeAvailable', () => {
it('should return true when at least one merge strategy is available', () => {
vm.mr.availableAutoMergeStrategies = [MWPS_MERGE_STRATEGY];
expect(vm.shouldShowAutoMergeText).toBeTruthy();
expect(vm.isAutoMergeAvailable).toBe(true);
});
it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false;
it('should return false when no merge strategies are available', () => {
vm.mr.availableAutoMergeStrategies = [];
expect(vm.shouldShowAutoMergeText).toBeFalsy();
expect(vm.isAutoMergeAvailable).toBe(false);
});
});
describe('status', () => {
it('defaults to success', () => {
vm.mr.pipeline = true;
Vue.set(vm.mr, 'pipeline', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.status).toEqual('success');
});
it('returns failed when MR has CI but also has an unknown status', () => {
vm.mr.hasCI = true;
Vue.set(vm.mr, 'hasCI', true);
expect(vm.status).toEqual('failed');
});
it('returns default when MR has no pipeline', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.status).toEqual('success');
});
it('returns pending when pipeline is active', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'pipeline', {});
Vue.set(vm.mr, 'isPipelineActive', true);
expect(vm.status).toEqual('pending');
});
it('returns failed when pipeline is failed', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineFailed = true;
Vue.set(vm.mr, 'pipeline', {});
Vue.set(vm.mr, 'isPipelineFailed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.status).toEqual('failed');
});
......@@ -143,18 +149,20 @@ describe('ReadyToMerge', () => {
const inActionClass = `${defaultClass} btn-info`;
it('defaults to success class', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.mergeButtonClass).toEqual(defaultClass);
});
it('returns success class for success status', () => {
vm.mr.pipeline = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'pipeline', true);
expect(vm.mergeButtonClass).toEqual(defaultClass);
});
it('returns info class for pending status', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
expect(vm.mergeButtonClass).toEqual(inActionClass);
});
......@@ -198,69 +206,82 @@ describe('ReadyToMerge', () => {
});
describe('mergeButtonText', () => {
it('should return Merge', () => {
it('should return "Merge" when no auto merge strategies are available', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.mergeButtonText).toEqual('Merge');
});
it('should return Merge in progress', () => {
vm.isMergingImmediately = true;
it('should return "Merge in progress"', () => {
Vue.set(vm, 'isMergingImmediately', true);
expect(vm.mergeButtonText).toEqual('Merge in progress');
});
it('should return Merge when pipeline succeeds', () => {
vm.isMergingImmediately = false;
vm.mr.isPipelineActive = true;
it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => {
Vue.set(vm, 'isMergingImmediately', false);
Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.mergeButtonText).toEqual('Merge when pipeline succeeds');
});
});
describe('autoMergeText', () => {
it('should return Merge when pipeline succeeds', () => {
Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.autoMergeText).toEqual('Merge when pipeline succeeds');
});
});
describe('shouldShowMergeOptionsDropdown', () => {
it('should return false with initial data', () => {
expect(vm.shouldShowMergeOptionsDropdown).toBeFalsy();
it('should return false when no auto merge strategies are available', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
});
it('should return true when pipeline active', () => {
vm.mr.isPipelineActive = true;
it('should return true when at least one auto merge strategy is available', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeOptionsDropdown).toBeTruthy();
expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
});
it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
vm.mr.isPipelineActive = true;
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
expect(vm.shouldShowMergeOptionsDropdown).toBeFalsy();
expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
});
});
describe('isMergeButtonDisabled', () => {
it('should return false with initial data', () => {
vm.mr.isMergeAllowed = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
expect(vm.isMergeButtonDisabled).toBeFalsy();
expect(vm.isMergeButtonDisabled).toBe(false);
});
it('should return true when there is no commit message', () => {
vm.mr.isMergeAllowed = true;
vm.commitMessage = '';
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm, 'commitMessage', '');
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
it('should return true if merge is not allowed', () => {
vm.mr.isMergeAllowed = false;
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
it('should return true when the vm instance is making request', () => {
vm.mr.isMergeAllowed = true;
vm.isMakingRequest = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm, 'isMakingRequest', true);
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
});
});
......@@ -268,31 +289,31 @@ describe('ReadyToMerge', () => {
describe('methods', () => {
describe('shouldShowMergeControls', () => {
it('should return false when an external pipeline is running and required to succeed', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = false;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeControls).toBeFalsy();
expect(vm.shouldShowMergeControls).toBe(false);
});
it('should return true when the build succeeded or build not required to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = false;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
});
......@@ -325,7 +346,6 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(true);
setTimeout(() => {
expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds');
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
......@@ -349,14 +369,13 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(false, true);
setTimeout(() => {
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
const params = vm.service.merge.calls.argsFor(0)[0];
expect(params.should_remove_source_branch).toBeTruthy();
expect(params.merge_when_pipeline_succeeds).toBeFalsy();
expect(params.auto_merge_strategy).toBeUndefined();
done();
}, 333);
});
......@@ -367,14 +386,13 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick();
setTimeout(() => {
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(vm.initiateMergePolling).toHaveBeenCalled();
const params = vm.service.merge.calls.argsFor(0)[0];
expect(params.should_remove_source_branch).toBeTruthy();
expect(params.merge_when_pipeline_succeeds).toBeFalsy();
expect(params.auto_merge_strategy).toBeUndefined();
done();
}, 333);
});
......
......@@ -25,7 +25,6 @@ export default {
},
merge_status: 'can_be_merged',
merge_user_id: null,
merge_when_pipeline_succeeds: false,
source_branch: 'daaaa',
source_branch_link: 'daaaa',
source_project_id: 19,
......@@ -210,8 +209,7 @@ export default {
source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/merge_requests/22/conflicts',
remove_wip_path: '/root/acets-app/merge_requests/22/remove_wip',
cancel_merge_when_pipeline_succeeds_path:
'/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds',
cancel_auto_merge_path: '/root/acets-app/merge_requests/22/cancel_auto_merge',
create_issue_to_resolve_discussions_path:
'/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22',
merge_path: '/root/acets-app/merge_requests/22/merge',
......@@ -237,6 +235,9 @@ export default {
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
merge_trains_count: 3,
merge_train_index: 1,
};
export const mockStore = {
......
......@@ -82,5 +82,47 @@ describe('MergeRequestStore', () => {
expect(store.isNothingToMergeState).toEqual(false);
});
});
describe('mergePipelinesEnabled', () => {
it('should set mergePipelinesEnabled = true when merge_pipelines_enabled is true', () => {
store.setData({ ...mockData, merge_pipelines_enabled: true });
expect(store.mergePipelinesEnabled).toBe(true);
});
it('should set mergePipelinesEnabled = false when merge_pipelines_enabled is not provided', () => {
store.setData({ ...mockData, merge_pipelines_enabled: undefined });
expect(store.mergePipelinesEnabled).toBe(false);
});
});
describe('mergeTrainsCount', () => {
it('should set mergeTrainsCount when merge_trains_count is provided', () => {
store.setData({ ...mockData, merge_trains_count: 3 });
expect(store.mergeTrainsCount).toBe(3);
});
it('should set mergeTrainsCount = 0 when merge_trains_count is not provided', () => {
store.setData({ ...mockData, merge_trains_count: undefined });
expect(store.mergeTrainsCount).toBe(0);
});
});
describe('mergeTrainIndex', () => {
it('should set mergeTrainIndex when merge_train_index is provided', () => {
store.setData({ ...mockData, merge_train_index: 3 });
expect(store.mergeTrainIndex).toBe(3);
});
it('should not set mergeTrainIndex when merge_train_index is not provided', () => {
store.setData({ ...mockData, merge_train_index: undefined });
expect(store.mergeTrainIndex).toBeUndefined();
});
});
});
});
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