2018-03-05 22:24:16 +00:00
|
|
|
<script>
|
2017-05-09 04:15:34 +00:00
|
|
|
import successSvg from 'icons/_icon_status_success.svg';
|
|
|
|
import warningSvg from 'icons/_icon_status_warning.svg';
|
|
|
|
import simplePoll from '~/lib/utils/simple_poll';
|
2018-01-15 17:06:38 +00:00
|
|
|
import MergeRequest from '../../../merge_request';
|
2017-10-02 12:32:53 +00:00
|
|
|
import Flash from '../../../flash';
|
2018-01-24 16:54:06 +00:00
|
|
|
import statusIcon from '../mr_widget_status_icon.vue';
|
2017-05-09 04:15:34 +00:00
|
|
|
import eventHub from '../../event_hub';
|
2018-09-21 10:07:17 +00:00
|
|
|
import SquashBeforeMerge from './squash_before_merge.vue';
|
2017-05-09 04:15:34 +00:00
|
|
|
|
|
|
|
export default {
|
2018-03-05 22:24:16 +00:00
|
|
|
name: 'ReadyToMerge',
|
|
|
|
components: {
|
|
|
|
statusIcon,
|
2019-01-15 09:11:34 +00:00
|
|
|
SquashBeforeMerge,
|
2018-03-05 22:24:16 +00:00
|
|
|
},
|
2017-05-09 04:15:34 +00:00
|
|
|
props: {
|
|
|
|
mr: { type: Object, required: true },
|
|
|
|
service: { type: Object, required: true },
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
2017-05-19 17:33:03 +00:00
|
|
|
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
|
2017-05-09 04:15:34 +00:00
|
|
|
mergeWhenBuildSucceeds: false,
|
|
|
|
useCommitMessageWithDescription: false,
|
|
|
|
setToMergeWhenPipelineSucceeds: false,
|
|
|
|
showCommitMessageEditor: false,
|
|
|
|
isMakingRequest: false,
|
|
|
|
isMergingImmediately: false,
|
|
|
|
commitMessage: this.mr.commitMessage,
|
2019-01-15 09:11:34 +00:00
|
|
|
squashBeforeMerge: this.mr.squash,
|
2017-05-09 04:15:34 +00:00
|
|
|
successSvg,
|
|
|
|
warningSvg,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
2017-09-01 00:46:18 +00:00
|
|
|
shouldShowMergeWhenPipelineSucceedsText() {
|
|
|
|
return this.mr.isPipelineActive;
|
|
|
|
},
|
2017-05-09 04:15:34 +00:00
|
|
|
commitMessageLinkTitle() {
|
|
|
|
const withDesc = 'Include description in commit message';
|
|
|
|
const withoutDesc = "Don't include description in commit message";
|
|
|
|
|
|
|
|
return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
|
|
|
|
},
|
2017-09-11 11:47:33 +00:00
|
|
|
status() {
|
2017-05-09 04:15:34 +00:00
|
|
|
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
|
|
|
|
|
|
|
|
if (hasCI && !ciStatus) {
|
2017-09-11 11:47:33 +00:00
|
|
|
return 'failed';
|
2017-05-09 04:15:34 +00:00
|
|
|
} else if (!pipeline) {
|
2017-09-11 11:47:33 +00:00
|
|
|
return 'success';
|
2017-05-09 04:15:34 +00:00
|
|
|
} else if (isPipelineActive) {
|
2017-09-11 11:47:33 +00:00
|
|
|
return 'pending';
|
2017-05-09 04:15:34 +00:00
|
|
|
} else if (isPipelineFailed) {
|
2017-09-11 11:47:33 +00:00
|
|
|
return 'failed';
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'success';
|
|
|
|
},
|
|
|
|
mergeButtonClass() {
|
|
|
|
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
|
|
|
|
const failedClass = `${defaultClass} btn-danger`;
|
|
|
|
const inActionClass = `${defaultClass} btn-info`;
|
|
|
|
|
|
|
|
if (this.status === 'failed') {
|
2017-05-09 04:15:34 +00:00
|
|
|
return failedClass;
|
2017-09-11 11:47:33 +00:00
|
|
|
} else if (this.status === 'pending') {
|
|
|
|
return inActionClass;
|
2017-05-09 04:15:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return defaultClass;
|
|
|
|
},
|
2017-09-11 11:47:33 +00:00
|
|
|
iconClass() {
|
2018-10-30 20:28:31 +00:00
|
|
|
if (
|
|
|
|
this.status === 'failed' ||
|
|
|
|
!this.commitMessage.length ||
|
|
|
|
!this.mr.isMergeAllowed ||
|
|
|
|
this.mr.preventMerge
|
|
|
|
) {
|
2018-01-19 15:56:34 +00:00
|
|
|
return 'warning';
|
2017-09-11 11:47:33 +00:00
|
|
|
}
|
|
|
|
return 'success';
|
|
|
|
},
|
2017-05-09 04:15:34 +00:00
|
|
|
mergeButtonText() {
|
|
|
|
if (this.isMergingImmediately) {
|
|
|
|
return 'Merge in progress';
|
2017-09-01 00:46:18 +00:00
|
|
|
} else if (this.shouldShowMergeWhenPipelineSucceedsText) {
|
2017-05-09 04:15:34 +00:00
|
|
|
return 'Merge when pipeline succeeds';
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'Merge';
|
|
|
|
},
|
|
|
|
shouldShowMergeOptionsDropdown() {
|
|
|
|
return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
|
|
|
|
},
|
|
|
|
isMergeButtonDisabled() {
|
|
|
|
const { commitMessage } = this;
|
2018-10-30 20:28:31 +00:00
|
|
|
return Boolean(
|
|
|
|
!commitMessage.length ||
|
|
|
|
!this.shouldShowMergeControls() ||
|
|
|
|
this.isMakingRequest ||
|
|
|
|
this.mr.preventMerge,
|
|
|
|
);
|
2017-05-09 04:15:34 +00:00
|
|
|
},
|
2017-05-19 17:33:03 +00:00
|
|
|
isRemoveSourceBranchButtonDisabled() {
|
2018-03-08 13:33:59 +00:00
|
|
|
return this.isMergeButtonDisabled;
|
2017-05-19 17:33:03 +00:00
|
|
|
},
|
2017-05-09 04:15:34 +00:00
|
|
|
shouldShowSquashBeforeMerge() {
|
|
|
|
const { commitsCount, enableSquashBeforeMerge } = this.mr;
|
|
|
|
return enableSquashBeforeMerge && commitsCount > 1;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
2017-09-01 00:46:18 +00:00
|
|
|
shouldShowMergeControls() {
|
2017-10-02 16:11:18 +00:00
|
|
|
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
|
2017-05-09 04:15:34 +00:00
|
|
|
},
|
|
|
|
updateCommitMessage() {
|
|
|
|
const cmwd = this.mr.commitMessageWithDescription;
|
|
|
|
this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
|
|
|
|
this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
|
|
|
|
},
|
|
|
|
toggleCommitMessageEditor() {
|
|
|
|
this.showCommitMessageEditor = !this.showCommitMessageEditor;
|
|
|
|
},
|
|
|
|
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
|
|
|
|
// TODO: Remove no-param-reassign
|
|
|
|
if (mergeWhenBuildSucceeds === undefined) {
|
|
|
|
mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
|
|
|
|
} else if (mergeImmediately) {
|
|
|
|
this.isMergingImmediately = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true;
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
sha: this.mr.sha,
|
|
|
|
commit_message: this.commitMessage,
|
|
|
|
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
|
|
|
|
should_remove_source_branch: this.removeSourceBranch === true,
|
2019-01-15 09:11:34 +00:00
|
|
|
squash: this.squashBeforeMerge,
|
2017-05-09 04:15:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.isMakingRequest = true;
|
2018-10-30 20:28:31 +00:00
|
|
|
this.service
|
|
|
|
.merge(options)
|
2017-12-15 06:11:00 +00:00
|
|
|
.then(res => res.data)
|
2018-10-30 20:28:31 +00:00
|
|
|
.then(data => {
|
2017-12-15 06:11:00 +00:00
|
|
|
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
|
2017-05-09 04:15:34 +00:00
|
|
|
|
2017-12-15 06:11:00 +00:00
|
|
|
if (data.status === 'merge_when_pipeline_succeeds') {
|
2017-05-09 04:15:34 +00:00
|
|
|
eventHub.$emit('MRWidgetUpdateRequested');
|
2017-12-15 06:11:00 +00:00
|
|
|
} else if (data.status === 'success') {
|
2017-05-09 04:15:34 +00:00
|
|
|
this.initiateMergePolling();
|
|
|
|
} else if (hasError) {
|
2017-12-15 06:11:00 +00:00
|
|
|
eventHub.$emit('FailedToMerge', data.merge_error);
|
2017-05-09 04:15:34 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
this.isMakingRequest = false;
|
|
|
|
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
|
|
|
|
});
|
|
|
|
},
|
|
|
|
initiateMergePolling() {
|
|
|
|
simplePoll((continuePolling, stopPolling) => {
|
|
|
|
this.handleMergePolling(continuePolling, stopPolling);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
handleMergePolling(continuePolling, stopPolling) {
|
2018-10-30 20:28:31 +00:00
|
|
|
this.service
|
|
|
|
.poll()
|
2017-12-15 06:11:00 +00:00
|
|
|
.then(res => res.data)
|
2018-10-30 20:28:31 +00:00
|
|
|
.then(data => {
|
2017-12-15 06:11:00 +00:00
|
|
|
if (data.state === 'merged') {
|
2017-05-09 04:15:34 +00:00
|
|
|
// If state is merged we should update the widget and stop the polling
|
|
|
|
eventHub.$emit('MRWidgetUpdateRequested');
|
|
|
|
eventHub.$emit('FetchActionsContent');
|
2018-01-17 14:09:59 +00:00
|
|
|
MergeRequest.setStatusBoxToMerged();
|
2018-01-15 17:06:38 +00:00
|
|
|
MergeRequest.hideCloseButton();
|
|
|
|
MergeRequest.decreaseCounter();
|
2017-05-09 04:15:34 +00:00
|
|
|
stopPolling();
|
|
|
|
|
|
|
|
// If user checked remove source branch and we didn't remove the branch yet
|
|
|
|
// we should start another polling for source branch remove process
|
2017-12-15 06:11:00 +00:00
|
|
|
if (this.removeSourceBranch && data.source_branch_exists) {
|
2017-05-09 04:15:34 +00:00
|
|
|
this.initiateRemoveSourceBranchPolling();
|
|
|
|
}
|
2017-12-15 06:11:00 +00:00
|
|
|
} else if (data.merge_error) {
|
|
|
|
eventHub.$emit('FailedToMerge', data.merge_error);
|
2017-05-09 04:15:34 +00:00
|
|
|
stopPolling();
|
|
|
|
} else {
|
|
|
|
// MR is not merged yet, continue polling until the state becomes 'merged'
|
|
|
|
continuePolling();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
|
|
|
|
});
|
|
|
|
},
|
|
|
|
initiateRemoveSourceBranchPolling() {
|
|
|
|
// We need to show source branch is being removed spinner in another component
|
|
|
|
eventHub.$emit('SetBranchRemoveFlag', [true]);
|
|
|
|
|
|
|
|
simplePoll((continuePolling, stopPolling) => {
|
|
|
|
this.handleRemoveBranchPolling(continuePolling, stopPolling);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
handleRemoveBranchPolling(continuePolling, stopPolling) {
|
2018-10-30 20:28:31 +00:00
|
|
|
this.service
|
|
|
|
.poll()
|
2017-12-15 06:11:00 +00:00
|
|
|
.then(res => res.data)
|
2018-10-30 20:28:31 +00:00
|
|
|
.then(data => {
|
2017-05-09 04:15:34 +00:00
|
|
|
// If source branch exists then we should continue polling
|
|
|
|
// because removing a source branch is a background task and takes time
|
2017-12-15 06:11:00 +00:00
|
|
|
if (data.source_branch_exists) {
|
2017-05-09 04:15:34 +00:00
|
|
|
continuePolling();
|
|
|
|
} else {
|
|
|
|
// Branch is removed. Update widget, stop polling and hide the spinner
|
|
|
|
eventHub.$emit('MRWidgetUpdateRequested', () => {
|
|
|
|
eventHub.$emit('SetBranchRemoveFlag', [false]);
|
|
|
|
});
|
|
|
|
stopPolling();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(() => {
|
2019-01-10 04:08:52 +00:00
|
|
|
new Flash('Something went wrong while deleting the source branch. Please try again.'); // eslint-disable-line
|
2017-05-09 04:15:34 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
},
|
2018-03-05 22:24:16 +00:00
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div class="mr-widget-body media">
|
|
|
|
<status-icon :status="iconClass" />
|
|
|
|
<div class="media-body">
|
|
|
|
<div class="mr-widget-body-controls media space-children">
|
2018-07-12 15:07:25 +00:00
|
|
|
<span class="btn-group">
|
2018-03-05 22:24:16 +00:00
|
|
|
<button
|
|
|
|
:disabled="isMergeButtonDisabled"
|
|
|
|
:class="mergeButtonClass"
|
|
|
|
type="button"
|
2018-06-11 09:49:47 +00:00
|
|
|
class="qa-merge-button"
|
2019-01-10 21:56:37 +00:00
|
|
|
@click="handleMergeButtonClick()"
|
2018-11-16 20:07:38 +00:00
|
|
|
>
|
|
|
|
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
|
2018-03-05 22:24:16 +00:00
|
|
|
{{ mergeButtonText }}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
v-if="shouldShowMergeOptionsDropdown"
|
|
|
|
:disabled="isMergeButtonDisabled"
|
|
|
|
type="button"
|
|
|
|
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
|
|
|
|
data-toggle="dropdown"
|
2018-11-16 20:07:38 +00:00
|
|
|
aria-label="Select merge moment"
|
|
|
|
>
|
|
|
|
<i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
|
2018-03-05 22:24:16 +00:00
|
|
|
</button>
|
|
|
|
<ul
|
|
|
|
v-if="shouldShowMergeOptionsDropdown"
|
|
|
|
class="dropdown-menu dropdown-menu-right"
|
2018-11-16 20:07:38 +00:00
|
|
|
role="menu"
|
|
|
|
>
|
2018-03-05 22:24:16 +00:00
|
|
|
<li>
|
|
|
|
<a
|
2018-09-20 15:48:38 +00:00
|
|
|
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
|
2018-06-11 09:49:47 +00:00
|
|
|
href="#"
|
2019-01-10 21:56:37 +00:00
|
|
|
@click.prevent="handleMergeButtonClick(true)"
|
2018-11-16 20:07:38 +00:00
|
|
|
>
|
2018-03-05 22:24:16 +00:00
|
|
|
<span class="media">
|
2018-11-16 20:07:38 +00:00
|
|
|
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
|
2018-03-05 22:24:16 +00:00
|
|
|
<span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
|
|
|
|
</span>
|
|
|
|
</a>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a
|
2018-09-20 15:48:38 +00:00
|
|
|
class="accept-merge-request qa-merge-immediately-option"
|
2018-06-11 09:49:47 +00:00
|
|
|
href="#"
|
2019-01-10 21:56:37 +00:00
|
|
|
@click.prevent="handleMergeButtonClick(false, true)"
|
2018-11-16 20:07:38 +00:00
|
|
|
>
|
2018-03-05 22:24:16 +00:00
|
|
|
<span class="media">
|
2018-11-16 20:07:38 +00:00
|
|
|
<span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
|
2018-03-05 22:24:16 +00:00
|
|
|
<span class="media-body merge-opt-title">Merge immediately</span>
|
|
|
|
</span>
|
|
|
|
</a>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</span>
|
|
|
|
<div class="media-body-wrap space-children">
|
|
|
|
<template v-if="shouldShowMergeControls()">
|
|
|
|
<label v-if="mr.canRemoveSourceBranch">
|
|
|
|
<input
|
|
|
|
id="remove-source-branch-input"
|
|
|
|
v-model="removeSourceBranch"
|
|
|
|
:disabled="isRemoveSourceBranchButtonDisabled"
|
2018-06-11 09:49:47 +00:00
|
|
|
class="js-remove-source-branch-checkbox"
|
2018-11-16 20:07:38 +00:00
|
|
|
type="checkbox"
|
|
|
|
/>
|
2019-01-10 04:08:52 +00:00
|
|
|
Delete source branch
|
2018-03-05 22:24:16 +00:00
|
|
|
</label>
|
|
|
|
|
|
|
|
<!-- Placeholder for EE extension of this component -->
|
|
|
|
<squash-before-merge
|
|
|
|
v-if="shouldShowSquashBeforeMerge"
|
2019-01-15 09:11:34 +00:00
|
|
|
v-model="squashBeforeMerge"
|
|
|
|
:help-path="mr.squashBeforeMergeHelpPath"
|
|
|
|
:is-disabled="isMergeButtonDisabled"
|
2018-11-16 20:07:38 +00:00
|
|
|
/>
|
2018-03-05 22:24:16 +00:00
|
|
|
|
2018-11-16 20:07:38 +00:00
|
|
|
<span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
|
2018-03-05 22:24:16 +00:00
|
|
|
Fast-forward merge without a merge commit
|
|
|
|
</span>
|
2017-08-07 02:29:37 +00:00
|
|
|
<button
|
2018-03-05 22:24:16 +00:00
|
|
|
v-else
|
2017-08-07 02:29:37 +00:00
|
|
|
:disabled="isMergeButtonDisabled"
|
2018-04-16 21:19:03 +00:00
|
|
|
class="js-modify-commit-message-button btn btn-default btn-sm"
|
2018-06-11 09:49:47 +00:00
|
|
|
type="button"
|
2018-11-16 20:07:38 +00:00
|
|
|
@click="toggleCommitMessageEditor"
|
|
|
|
>
|
2018-03-05 22:24:16 +00:00
|
|
|
Modify commit message
|
2017-08-07 02:29:37 +00:00
|
|
|
</button>
|
2018-03-05 22:24:16 +00:00
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<span class="bold js-resolve-mr-widget-items-message">
|
|
|
|
You can only merge once the items above are resolved
|
|
|
|
</span>
|
|
|
|
</template>
|
2017-08-07 02:29:37 +00:00
|
|
|
</div>
|
2018-03-05 22:24:16 +00:00
|
|
|
</div>
|
2018-11-16 20:07:38 +00:00
|
|
|
<div v-if="showCommitMessageEditor" class="prepend-top-default commit-message-editor">
|
2018-03-05 22:24:16 +00:00
|
|
|
<div class="form-group clearfix">
|
2018-11-16 20:07:38 +00:00
|
|
|
<label class="col-form-label" for="commit-message"> Commit message </label>
|
2018-03-05 22:24:16 +00:00
|
|
|
<div class="col-sm-10">
|
|
|
|
<div class="commit-message-container">
|
|
|
|
<div class="max-width-marker"></div>
|
|
|
|
<textarea
|
|
|
|
id="commit-message"
|
|
|
|
v-model="commitMessage"
|
|
|
|
class="form-control js-commit-message"
|
|
|
|
required="required"
|
|
|
|
rows="14"
|
2018-11-16 20:07:38 +00:00
|
|
|
name="Commit message"
|
|
|
|
></textarea>
|
2018-03-05 22:24:16 +00:00
|
|
|
</div>
|
|
|
|
<p class="hint">
|
|
|
|
Try to keep the first line under 52 characters and the others under 72
|
|
|
|
</p>
|
|
|
|
<div class="hint">
|
2018-11-16 20:07:38 +00:00
|
|
|
<a href="#" @click.prevent="updateCommitMessage"> {{ commitMessageLinkTitle }} </a>
|
2017-05-09 04:15:34 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2017-08-07 02:29:37 +00:00
|
|
|
</div>
|
2017-05-09 04:15:34 +00:00
|
|
|
</div>
|
2018-03-05 22:24:16 +00:00
|
|
|
</div>
|
|
|
|
</template>
|