diff options
author | Nathan Friend <nathan@gitlab.com> | 2019-06-11 17:19:22 +0300 |
---|---|---|
committer | Nathan Friend <nathan@gitlab.com> | 2019-06-11 17:27:51 +0300 |
commit | 35ae9d8a7449ebe4b5c1878825bcb931a82e7a59 (patch) | |
tree | 4e5c698fbcce2c9bffa0a1cdfe1ce449f750ad05 | |
parent | fc27c93e4b060db069f1651d1395d59e121595f7 (diff) |
Add merge train support to MR merge button (CE)
This commit updates the merge request widget's "Merge" button to
support merge trains.
14 files changed, 273 insertions, 104 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index 88e1ccbaf35..5958c2cf87e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -1,15 +1,19 @@ <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"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 615d59a7b8e..ca1b4a57717 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -1,4 +1,5 @@ <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> diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js index 0a29d55fbd6..3e65bdf0cb0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/constants.js @@ -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, +]; diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/auto_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/auto_merge.js new file mode 100644 index 00000000000..23e140623cc --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/auto_merge.js @@ -0,0 +1,15 @@ +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'); + }, + }, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js index b2e64506472..116d537c463 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js @@ -1,3 +1,5 @@ +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'); + }, }, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index d02bb2f341d..a39c4911dce 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -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, diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 32badb0fb08..bfa3e7f4a59 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -1,7 +1,9 @@ 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; + } } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 48bc6a867f4..28507bba3e5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -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', diff --git a/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml b/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml new file mode 100644 index 00000000000..2c1d56f197e --- /dev/null +++ b/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml @@ -0,0 +1,5 @@ +--- +title: Update the merge request widget's "Merge" button to support merge trains +merge_request: 27594 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 20c8a834d04..136fd6f9e3e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6045,6 +6045,9 @@ msgstr "" msgid "Members of <strong>%{project_name}</strong>" msgstr "" +msgid "Merge" +msgstr "" + msgid "Merge Request" msgstr "" diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js index 8e0415b813b..2ea8c169add 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js @@ -1,17 +1,19 @@ 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(); + }); + }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 3ae773b6ccb..bb76616be56 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -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); }); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index edbd0d54151..3c9a5cece90 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -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 = { diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js index e2cd0f084fd..e27a506f426 100644 --- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js @@ -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(); + }); + }); }); }); |