Verified Commit 35ae9d8a authored by Nathan Friend's avatar Nathan Friend
Browse files

Add merge train support to MR merge button (CE)

This commit updates the merge request widget's "Merge" button to
support merge trains.
parent fc27c93e
Loading
Loading
Loading
Loading
+10 −6
Original line number Original line Diff line number Diff line
<script>
<script>
import _ from 'underscore';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import Flash from '../../../flash';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import eventHub from '../../event_hub';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';


export default {
export default {
  name: 'MRWidgetMergeWhenPipelineSucceeds',
  name: 'MRWidgetAutoMergeEnabled',
  components: {
  components: {
    MrWidgetAuthor,
    MrWidgetAuthor,
    statusIcon,
    statusIcon,
  },
  },
  mixins: [autoMergeMixin],
  props: {
  props: {
    mr: {
    mr: {
      type: Object,
      type: Object,
@@ -57,7 +61,7 @@ export default {
    removeSourceBranch() {
    removeSourceBranch() {
      const options = {
      const options = {
        sha: this.mr.sha,
        sha: this.mr.sha,
        auto_merge_strategy: 'merge_when_pipeline_succeeds',
        auto_merge_strategy: this.mr.autoMergeStrategy,
        should_remove_source_branch: true,
        should_remove_source_branch: true,
      };
      };


@@ -66,7 +70,7 @@ export default {
        .merge(options)
        .merge(options)
        .then(res => res.data)
        .then(res => res.data)
        .then(data => {
        .then(data => {
          if (data.status === 'merge_when_pipeline_succeeds') {
          if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
            eventHub.$emit('MRWidgetUpdateRequested');
            eventHub.$emit('MRWidgetUpdateRequested');
          }
          }
        })
        })
@@ -84,9 +88,9 @@ export default {
    <div class="media-body">
    <div class="media-body">
      <h4 class="d-flex align-items-start">
      <h4 class="d-flex align-items-start">
        <span class="append-right-10">
        <span class="append-right-10">
          {{ s__('mrWidget|Set by') }}
          <span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span>
          <mr-widget-author :author="mr.setToAutoMergeBy" />
          <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>
        </span>
        <a
        <a
          v-if="mr.canCancelAutomaticMerge"
          v-if="mr.canCancelAutomaticMerge"
@@ -97,7 +101,7 @@ export default {
          @click.prevent="cancelAutomaticMerge"
          @click.prevent="cancelAutomaticMerge"
        >
        >
          <i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
          <i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
          {{ s__('mrWidget|Cancel automatic merge') }}
          {{ cancelButtonText }}
        </a>
        </a>
      </h4>
      </h4>
      <section class="mr-info-list">
      <section class="mr-info-list">
+20 −27
Original line number Original line Diff line number Diff line
<script>
<script>
import _ from 'underscore';
import successSvg from 'icons/_icon_status_success.svg';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
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 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';


export default {
export default {
  name: 'ReadyToMerge',
  name: 'ReadyToMerge',
@@ -30,8 +32,6 @@ export default {
  data() {
  data() {
    return {
    return {
      removeSourceBranch: this.mr.shouldRemoveSourceBranch,
      removeSourceBranch: this.mr.shouldRemoveSourceBranch,
      mergeWhenBuildSucceeds: false,
      autoMergeStrategy: undefined,
      isMakingRequest: false,
      isMakingRequest: false,
      isMergingImmediately: false,
      isMergingImmediately: false,
      commitMessage: this.mr.commitMessage,
      commitMessage: this.mr.commitMessage,
@@ -42,18 +42,18 @@ export default {
    };
    };
  },
  },
  computed: {
  computed: {
    shouldShowAutoMergeText() {
    isAutoMergeAvailable() {
      return this.mr.isPipelineActive;
      return !_.isEmpty(this.mr.availableAutoMergeStrategies);
    },
    },
    status() {
    status() {
      const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
      const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;


      if (hasCI && !ciStatus) {
      if (hasCI && !ciStatus) {
        return 'failed';
        return 'failed';
      } else if (this.isAutoMergeAvailable) {
        return 'pending';
      } else if (!pipeline) {
      } else if (!pipeline) {
        return 'success';
        return 'success';
      } else if (isPipelineActive) {
        return 'pending';
      } else if (isPipelineFailed) {
      } else if (isPipelineFailed) {
        return 'failed';
        return 'failed';
      }
      }
@@ -87,14 +87,14 @@ export default {
    mergeButtonText() {
    mergeButtonText() {
      if (this.isMergingImmediately) {
      if (this.isMergingImmediately) {
        return __('Merge in progress');
        return __('Merge in progress');
      } else if (this.shouldShowAutoMergeText) {
      } else if (this.isAutoMergeAvailable) {
        return __('Merge when pipeline succeeds');
        return this.autoMergeText;
      }
      }


      return 'Merge';
      return __('Merge');
    },
    },
    shouldShowMergeOptionsDropdown() {
    shouldShowMergeOptionsDropdown() {
      return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
      return this.isAutoMergeAvailable && !this.mr.onlyAllowMergeIfPipelineSucceeds;
    },
    },
    isRemoveSourceBranchButtonDisabled() {
    isRemoveSourceBranchButtonDisabled() {
      return this.isMergeButtonDisabled;
      return this.isMergeButtonDisabled;
@@ -104,7 +104,7 @@ export default {
      return enableSquashBeforeMerge && commitsCount > 1;
      return enableSquashBeforeMerge && commitsCount > 1;
    },
    },
    shouldShowMergeControls() {
    shouldShowMergeControls() {
      return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
      return this.mr.isMergeAllowed || this.isAutoMergeAvailable;
    },
    },
    shouldShowSquashEdit() {
    shouldShowSquashEdit() {
      return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
      return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
@@ -118,20 +118,15 @@ export default {
      const { commitMessageWithDescription, commitMessage } = this.mr;
      const { commitMessageWithDescription, commitMessage } = this.mr;
      this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
      this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
    },
    },
    handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
    handleMergeButtonClick(useAutoMerge, mergeImmediately = false) {
      // TODO: Remove no-param-reassign
      if (mergeImmediately) {
      if (mergeWhenBuildSucceeds === undefined) {
        mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
      } else if (mergeImmediately) {
        this.isMergingImmediately = true;
        this.isMergingImmediately = true;
      }
      }


      this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;

      const options = {
      const options = {
        sha: this.mr.sha,
        sha: this.mr.sha,
        commit_message: this.commitMessage,
        commit_message: this.commitMessage,
        auto_merge_strategy: this.autoMergeStrategy,
        auto_merge_strategy: useAutoMerge ? this.mr.preferredAutoMergeStrategy : undefined,
        should_remove_source_branch: this.removeSourceBranch === true,
        should_remove_source_branch: this.removeSourceBranch === true,
        squash: this.squashBeforeMerge,
        squash: this.squashBeforeMerge,
        squash_commit_message: this.squashCommitMessage,
        squash_commit_message: this.squashCommitMessage,
@@ -144,7 +139,7 @@ export default {
        .then(data => {
        .then(data => {
          const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
          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');
            eventHub.$emit('MRWidgetUpdateRequested');
          } else if (data.status === 'success') {
          } else if (data.status === 'success') {
            this.initiateMergePolling();
            this.initiateMergePolling();
@@ -242,13 +237,13 @@ export default {
              :class="mergeButtonClass"
              :class="mergeButtonClass"
              type="button"
              type="button"
              class="qa-merge-button"
              class="qa-merge-button"
              @click="handleMergeButtonClick()"
              @click="handleMergeButtonClick(isAutoMergeAvailable)"
            >
            >
              <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
              <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
              {{ mergeButtonText }}
              {{ mergeButtonText }}
            </button>
            </button>
            <button
            <button
              v-if="shouldShowMergeOptionsDropdown"
              v-if="isAutoMergeAvailable"
              :disabled="isMergeButtonDisabled"
              :disabled="isMergeButtonDisabled"
              type="button"
              type="button"
              class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
              class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
@@ -264,15 +259,13 @@ export default {
            >
            >
              <li>
              <li>
                <a
                <a
                  class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
                  class="auto_merge_enabled qa-merge-when-pipeline-succeeds-option"
                  href="#"
                  href="#"
                  @click.prevent="handleMergeButtonClick(true)"
                  @click.prevent="handleMergeButtonClick(true)"
                >
                >
                  <span class="media">
                  <span class="media">
                    <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
                    <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
                    <span class="media-body merge-opt-title">{{
                    <span class="media-body merge-opt-title">{{ autoMergeText }}</span>
                      __('Merge when pipeline succeeds')
                    }}</span>
                  </span>
                  </span>
                </a>
                </a>
              </li>
              </li>
+10 −0
Original line number Original line Diff line number Diff line
@@ -3,3 +3,13 @@ export const DANGER = 'danger';


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';

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,
];
+15 −0
Original line number Original line Diff line number Diff line
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');
    },
  },
};
+6 −0
Original line number Original line Diff line number Diff line
import { __ } from '~/locale';

export default {
export default {
  computed: {
  computed: {
    isMergeButtonDisabled() {
    isMergeButtonDisabled() {
@@ -9,5 +11,9 @@ export default {
          this.mr.preventMerge,
          this.mr.preventMerge,
      );
      );
    },
    },
    autoMergeText() {
      // MWPS is currently the only auto merge strategy available in CE
      return __('Merge when pipeline succeeds');
    },
  },
  },
};
};
Loading