diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components/states')
18 files changed, 982 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js new file mode 100644 index 00000000000..c7f25a1697c --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js @@ -0,0 +1,16 @@ +export default { + name: 'MRWidgetArchived', + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + This project is archived, write access has been disabled. + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js new file mode 100644 index 00000000000..fcccb17f58d --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js @@ -0,0 +1,22 @@ +export default { + name: 'MRWidgetAutoMergeFailed', + props: { + mr: { type: Object, required: true }, + }, + template: ` + <div class="mr-widget-body"> + <button + class="btn btn-success btn-small" + disabled="true" + type="button"> + Merge + </button> + <span class="bold danger"> + This merge request failed to be merged automatically. + </span> + <div class="merge-error-text"> + {{mr.mergeError}} + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js new file mode 100644 index 00000000000..8515b54e62d --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js @@ -0,0 +1,19 @@ +export default { + name: 'MRWidgetChecking', + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + Checking ability to merge automatically. + <i + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js new file mode 100644 index 00000000000..7e66441e5ff --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js @@ -0,0 +1,30 @@ +import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; + +export default { + name: 'MRWidgetClosed', + props: { + mr: { type: Object, required: true }, + }, + components: { + 'mr-widget-author-and-time': mrWidgetAuthorTime, + }, + template: ` + <div class="mr-widget-body"> + <mr-widget-author-and-time + actionText="Closed by" + :author="mr.closedBy" + :dateTitle="mr.updatedAt" + :dateReadable="mr.closedAt" + /> + <section> + <p> + The changes were not merged into + <a + :href="mr.targetBranchCommitsPath" + class="label-branch"> + {{mr.targetBranch}}</a>. + </p> + </section> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js new file mode 100644 index 00000000000..36596c6f37e --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js @@ -0,0 +1,39 @@ +export default { + name: 'MRWidgetConflicts', + props: { + mr: { type: Object, required: true }, + }, + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + There are merge conflicts. + <span v-if="!mr.canMerge"> + Resolve these conflicts or ask someone with write access to this repository to merge it locally. + </span> + </span> + <div + v-if="mr.canMerge" + class="btn-group"> + <a + v-if="mr.conflictResolutionPath" + :href="mr.conflictResolutionPath" + class="btn btn-default btn-xs js-resolve-conflicts-button"> + Resolve conflicts + </a> + <a + v-if="mr.canMerge" + class="btn btn-default btn-xs js-merge-locally-button" + data-toggle="modal" + href="#modal_merge_info"> + Merge locally + </a> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js new file mode 100644 index 00000000000..600b4d42e3d --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js @@ -0,0 +1,76 @@ +import eventHub from '../../event_hub'; + +export default { + name: 'MRWidgetFailedToMerge', + props: { + mr: { type: Object, required: true }, + }, + data() { + return { + timer: 10, + isRefreshing: false, + }; + }, + mounted() { + setInterval(() => { + this.updateTimer(); + }, 1000); + }, + created() { + eventHub.$emit('DisablePolling'); + }, + computed: { + timerText() { + return this.timer > 1 ? `${this.timer} seconds` : 'a second'; + }, + }, + methods: { + refresh() { + this.isRefreshing = true; + eventHub.$emit('MRWidgetUpdateRequested'); + eventHub.$emit('EnablePolling'); + }, + updateTimer() { + this.timer = this.timer - 1; + + if (this.timer === 0) { + this.refresh(); + } + }, + }, + template: ` + <div class="mr-widget-body"> + <button + class="btn btn-success btn-small" + disabled="true" + type="button"> + Merge + </button> + <span + v-if="!isRefreshing" + class="bold danger"> + <span + class="has-error-message" + v-if="mr.mergeError"> + {{mr.mergeError}} + </span> + <span v-else>Merge failed.</span> + <span + :class="{ 'has-custom-error': mr.mergeError }"> + Refreshing in {{timerText}} to show the updated status... + </span> + <button + @click="refresh" + class="btn btn-default btn-xs js-refresh-button" + type="button"> + Refresh now + </button> + </span> + <span + v-if="isRefreshing" + class="bold js-refresh-label"> + Refreshing now... + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js new file mode 100644 index 00000000000..e3c27dfb76d --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js @@ -0,0 +1,24 @@ +export default { + name: 'MRWidgetLocked', + props: { + mr: { type: Object, required: true }, + }, + template: ` + <div class="mr-widget-body mr-state-locked"> + <span class="state-label">Locked</span> + This merge request is in the process of being merged, during which time it is locked and cannot be closed. + <i + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + <section class="mr-info-list mr-links"> + <div class="legend"></div> + <p> + The changes will be merged into + <span class="label-branch"> + <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> + </span> + </p> + </section> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js new file mode 100644 index 00000000000..bcdbedcd46b --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js @@ -0,0 +1,116 @@ +/* global Flash */ + +import MRWidgetAuthor from '../../components/mr_widget_author'; +import eventHub from '../../event_hub'; + +export default { + name: 'MRWidgetMergeWhenPipelineSucceeds', + props: { + mr: { type: Object, required: true }, + service: { type: Object, required: true }, + }, + components: { + 'mr-widget-author': MRWidgetAuthor, + }, + data() { + return { + isCancellingAutoMerge: false, + isRemovingSourceBranch: false, + }; + }, + computed: { + canRemoveSourceBranch() { + const { shouldRemoveSourceBranch, canRemoveSourceBranch, + mergeUserId, currentUserId } = this.mr; + + return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId; + }, + }, + methods: { + cancelAutomaticMerge() { + this.isCancellingAutoMerge = true; + this.service.cancelAutomaticMerge() + .then(res => res.json()) + .then((res) => { + eventHub.$emit('UpdateWidgetData', res); + }) + .catch(() => { + this.isCancellingAutoMerge = false; + new Flash('Something went wrong. Please try again.'); // eslint-disable-line + }); + }, + removeSourceBranch() { + const options = { + sha: this.mr.sha, + merge_when_pipeline_succeeds: true, + should_remove_source_branch: true, + }; + + this.isRemovingSourceBranch = true; + this.service.mergeResource.save(options) + .then(res => res.json()) + .then((res) => { + if (res.status === 'merge_when_pipeline_succeeds') { + eventHub.$emit('MRWidgetUpdateRequested'); + } + }) + .catch(() => { + this.isRemovingSourceBranch = false; + new Flash('Something went wrong. Please try again.'); // eslint-disable-line + }); + }, + }, + template: ` + <div class="mr-widget-body"> + <h4> + Set by + <mr-widget-author :author="mr.setToMWPSBy" /> + to be merged automatically when the pipeline succeeds. + <a + v-if="mr.canCancelAutomaticMerge" + @click.prevent="cancelAutomaticMerge" + :disabled="isCancellingAutoMerge" + role="button" + href="#" + class="btn btn-xs btn-default js-cancel-auto-merge"> + <i + v-if="isCancellingAutoMerge" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + Cancel automatic merge + </a> + </h4> + <section class="mr-info-list"> + <div class="legend"></div> + <p>The changes will be merged into + <a + :href="mr.targetBranchPath" + class="label-branch"> + {{mr.targetBranch}} + </a> + </p> + <p v-if="mr.shouldRemoveSourceBranch"> + The source branch will be removed. + </p> + <p + v-else + class="with-button"> + The source branch will not be removed. + <a + v-if="canRemoveSourceBranch" + :disabled="isRemovingSourceBranch" + @click.prevent="removeSourceBranch" + role="button" + class="btn btn-xs btn-default js-remove-source-branch" + href="#"> + <i + v-if="isRemovingSourceBranch" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + Remove source branch + </a> + </p> + </section> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js new file mode 100644 index 00000000000..c7d32d18141 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js @@ -0,0 +1,130 @@ +/* global Flash */ + +import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; +import eventHub from '../../event_hub'; + +export default { + name: 'MRWidgetMerged', + props: { + mr: { type: Object, required: true }, + service: { type: Object, required: true }, + }, + components: { + 'mr-widget-author-and-time': mrWidgetAuthorTime, + }, + data() { + return { + isMakingRequest: false, + }; + }, + computed: { + shouldShowRemoveSourceBranch() { + const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr; + + return !sourceBranchRemoved && canRemoveSourceBranch && + !this.isMakingRequest && !isRemovingSourceBranch; + }, + shouldShowSourceBranchRemoving() { + const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr; + return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest); + }, + shouldShowMergedButtons() { + const { canRevertInCurrentMR, canCherryPickInCurrentMR, revertInForkPath, + cherryPickInForkPath } = this.mr; + + return canRevertInCurrentMR || canCherryPickInCurrentMR || + revertInForkPath || cherryPickInForkPath; + }, + }, + methods: { + removeSourceBranch() { + this.isMakingRequest = true; + this.service.removeSourceBranch() + .then(res => res.json()) + .then((res) => { + if (res.message === 'Branch was removed') { + eventHub.$emit('MRWidgetUpdateRequested', () => { + this.isMakingRequest = false; + }); + } + }) + .catch(() => { + this.isMakingRequest = false; + new Flash('Something went wrong. Please try again.'); // eslint-disable-line + }); + }, + }, + template: ` + <div class="mr-widget-body"> + <mr-widget-author-and-time + actionText="Merged by" + :author="mr.mergedBy" + :dateTitle="mr.updatedAt" + :dateReadable="mr.mergedAt" /> + <section class="mr-info-list"> + <div class="legend"></div> + <p> + The changes were merged into + <span class="label-branch"> + <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> + </span> + </p> + <p v-if="mr.sourceBranchRemoved">The source branch has been removed.</p> + <p v-if="shouldShowRemoveSourceBranch"> + You can remove source branch now. + <button + @click="removeSourceBranch" + :class="{ disabled: isMakingRequest }" + type="button" + class="btn btn-xs btn-default js-remove-branch-button"> + Remove Source Branch + </button> + </p> + <p v-if="shouldShowSourceBranchRemoving"> + <i + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + The source branch is being removed. + </p> + </section> + <div + v-if="shouldShowMergedButtons" + class="merged-buttons clearfix"> + <a + v-if="mr.canRevertInCurrentMR" + class="btn btn-close btn-sm has-tooltip" + href="#modal-revert-commit" + data-toggle="modal" + data-container="body" + title="Revert this merge request in a new merge request"> + Revert + </a> + <a + v-else-if="mr.revertInForkPath" + class="btn btn-close btn-sm has-tooltip" + data-method="post" + :href="mr.revertInForkPath" + title="Revert this merge request in a new merge request"> + Revert + </a> + <a + v-if="mr.canCherryPickInCurrentMR" + class="btn btn-default btn-sm has-tooltip" + href="#modal-cherry-pick-commit" + data-toggle="modal" + data-container="body" + title="Cherry-pick this merge request in a new merge request"> + Cherry-pick + </a> + <a + v-else-if="mr.cherryPickInForkPath" + class="btn btn-default btn-sm has-tooltip" + data-method="post" + :href="mr.cherryPickInForkPath" + title="Cherry-pick this merge request in a new merge request"> + Cherry-pick + </a> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js new file mode 100644 index 00000000000..328382485f6 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js @@ -0,0 +1,34 @@ +import mrWidgetMergeHelp from '../../components/mr_widget_merge_help'; + +export default { + name: 'MRWidgetMissingBranch', + props: { + mr: { type: Object, required: true }, + }, + components: { + 'mr-widget-merge-help': mrWidgetMergeHelp, + }, + computed: { + missingBranchName() { + return this.mr.sourceBranchRemoved ? 'source' : 'target'; + }, + }, + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold js-branch-text"> + <span class="capitalize"> + {{missingBranchName}} + </span> branch does not exist. + Please restore the {{missingBranchName}} branch or use a different {{missingBranchName}} branch. + </span> + <mr-widget-merge-help + :missing-branch="missingBranchName" /> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js new file mode 100644 index 00000000000..07169b349be --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js @@ -0,0 +1,17 @@ +export default { + name: 'MRWidgetNotAllowed', + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + Ready to be merged automatically. + Ask someone with write access to this repository to merge this request. + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js new file mode 100644 index 00000000000..8c4535f1337 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js @@ -0,0 +1,17 @@ +export default { + name: 'MRWidgetNothingToMerge', + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + There is nothing to merge from source branch into target branch. + Please push new commits or use a different branch. + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js new file mode 100644 index 00000000000..31c53b679ed --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js @@ -0,0 +1,16 @@ +export default { + name: 'MRWidgetPipelineBlocked', + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + Pipeline blocked. The pipeline for this merge request requires a manual action to proceed. + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js new file mode 100644 index 00000000000..002820123ca --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js @@ -0,0 +1,16 @@ +export default { + name: 'MRWidgetPipelineBlocked', + template: ` + <div class="mr-widget-body"> + <button + class="btn btn-success btn-small" + disabled="true" + type="button"> + Merge + </button> + <span class="bold"> + The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure. + </span> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js new file mode 100644 index 00000000000..ebcc03e531b --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -0,0 +1,309 @@ +/* global Flash */ + +import successSvg from 'icons/_icon_status_success.svg'; +import warningSvg from 'icons/_icon_status_warning.svg'; +import simplePoll from '~/lib/utils/simple_poll'; +import eventHub from '../../event_hub'; + +export default { + name: 'MRWidgetReadyToMerge', + props: { + mr: { type: Object, required: true }, + service: { type: Object, required: true }, + }, + data() { + return { + removeSourceBranch: true, + mergeWhenBuildSucceeds: false, + useCommitMessageWithDescription: false, + setToMergeWhenPipelineSucceeds: false, + showCommitMessageEditor: false, + isMakingRequest: false, + isMergingImmediately: false, + commitMessage: this.mr.commitMessage, + successSvg, + warningSvg, + }; + }, + computed: { + commitMessageLinkTitle() { + const withDesc = 'Include description in commit message'; + const withoutDesc = "Don't include description in commit message"; + + return this.useCommitMessageWithDescription ? withoutDesc : withDesc; + }, + mergeButtonClass() { + const defaultClass = 'btn btn-success accept-merge-request'; + const failedClass = `${defaultClass} btn-danger`; + const inActionClass = `${defaultClass} btn-info`; + const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr; + + if (hasCI && !ciStatus) { + return failedClass; + } else if (!pipeline) { + return defaultClass; + } else if (isPipelineActive) { + return inActionClass; + } else if (isPipelineFailed) { + return failedClass; + } + + return defaultClass; + }, + mergeButtonText() { + if (this.isMergingImmediately) { + return 'Merge in progress'; + } else if (this.mr.isPipelineActive) { + return 'Merge when pipeline succeeds'; + } + + return 'Merge'; + }, + shouldShowMergeOptionsDropdown() { + return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds; + }, + isMergeButtonDisabled() { + const { commitMessage } = this; + return Boolean(!commitMessage.length + || !this.isMergeAllowed() + || this.isMakingRequest + || this.mr.preventMerge); + }, + shouldShowSquashBeforeMerge() { + const { commitsCount, enableSquashBeforeMerge } = this.mr; + return enableSquashBeforeMerge && commitsCount > 1; + }, + }, + methods: { + isMergeAllowed() { + return !(this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.isPipelineFailed); + }, + 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, + }; + + // Only truthy in EE extension of this component + if (this.setAdditionalParams) { + this.setAdditionalParams(options); + } + + this.isMakingRequest = true; + this.service.merge(options) + .then(res => res.json()) + .then((res) => { + const hasError = res.status === 'failed' || res.status === 'hook_validation_error'; + + if (res.status === 'merge_when_pipeline_succeeds') { + eventHub.$emit('MRWidgetUpdateRequested'); + } else if (res.status === 'success') { + this.initiateMergePolling(); + } else if (hasError) { + eventHub.$emit('FailedToMerge', res.merge_error); + } + }) + .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) { + this.service.poll() + .then(res => res.json()) + .then((res) => { + if (res.state === 'merged') { + // If state is merged we should update the widget and stop the polling + eventHub.$emit('MRWidgetUpdateRequested'); + eventHub.$emit('FetchActionsContent'); + if (window.mergeRequest) { + window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); + window.mergeRequest.decreaseCounter(); + } + 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 + if (this.removeSourceBranch && res.source_branch_exists) { + this.initiateRemoveSourceBranchPolling(); + } + } else if (res.merge_error) { + eventHub.$emit('FailedToMerge', res.merge_error); + 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) { + this.service.poll() + .then(res => res.json()) + .then((res) => { + // If source branch exists then we should continue polling + // because removing a source branch is a background task and takes time + if (res.source_branch_exists) { + continuePolling(); + } else { + // Branch is removed. Update widget, stop polling and hide the spinner + eventHub.$emit('MRWidgetUpdateRequested', () => { + eventHub.$emit('SetBranchRemoveFlag', [false]); + }); + stopPolling(); + } + }) + .catch(() => { + new Flash('Something went wrong while removing the source branch. Please try again.'); // eslint-disable-line + }); + }, + }, + template: ` + <div class="mr-widget-body"> + <span class="btn-group"> + <button + @click="handleMergeButtonClick()" + :disabled="isMergeButtonDisabled" + :class="mergeButtonClass" + type="button"> + <i + v-if="isMakingRequest" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + {{mergeButtonText}} + </button> + <button + v-if="shouldShowMergeOptionsDropdown" + :disabled="isMergeButtonDisabled" + type="button" + class="btn btn-info dropdown-toggle" + data-toggle="dropdown"> + <i + class="fa fa-caret-down" + aria-hidden="true" /> + <span class="sr-only"> + Select merge moment + </span> + </button> + <ul + v-if="shouldShowMergeOptionsDropdown" + class="dropdown-menu dropdown-menu-right" + role="menu"> + <li> + <a + @click.prevent="handleMergeButtonClick(true)" + class="merge_when_pipeline_succeeds" + href="#"> + <span + v-html="successSvg" + class="merge-opt-icon" + aria-hidden="true"></span> + <span class="merge-opt-title">Merge when pipeline succeeds</span> + </a> + </li> + <li> + <a + @click.prevent="handleMergeButtonClick(false, true)" + class="accept-merge-request" + href="#"> + <span + v-html="warningSvg" + class="merge-opt-icon" + aria-hidden="true"></span> + <span class="merge-opt-title">Merge immediately</span> + </a> + </li> + </ul> + </span> + <template v-if="isMergeAllowed()"> + <label class="spacing"> + <input + v-model="removeSourceBranch" + :disabled="isMergeButtonDisabled" + type="checkbox"/> Remove source branch + </label> + + <!-- Placeholder for EE extension of this component --> + <squash-before-merge + v-if="shouldShowSquashBeforeMerge" + :mr="mr" + :is-merge-button-disabled="isMergeButtonDisabled" /> + + <button + @click="toggleCommitMessageEditor" + :disabled="isMergeButtonDisabled" + class="btn btn-default btn-xs" + type="button"> + Modify commit message + </button> + <div + v-if="showCommitMessageEditor" + class="prepend-top-default commit-message-editor"> + <div class="form-group clearfix"> + <label + class="control-label" + for="commit-message"> + Commit message + </label> + <div class="col-sm-10"> + <div class="commit-message-container"> + <div class="max-width-marker"></div> + <textarea + v-model="commitMessage" + class="form-control js-commit-message" + required="required" + rows="14" + name="Commit message"></textarea> + </div> + <p class="hint">Try to keep the first line under 52 characters and the others under 72.</p> + <div class="hint"> + <a + @click.prevent="updateCommitMessage" + href="#">{{commitMessageLinkTitle}}</a> + </div> + </div> + </div> + </div> + </template> + <template v-else> + <span class="bold"> + The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure. + </span> + </template> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js new file mode 100644 index 00000000000..bf8628d18a6 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js @@ -0,0 +1,15 @@ +/* +The squash-before-merge button is EE only, but it's located right in the middle +of the readyToMerge state component template. + +If we didn't declare this component in CE, we'd need to maintain a separate copy +of the readyToMergeState template in EE, which is pretty big and likely to change. + +Instead, in CE, we declare the component, but it's hidden and is configured to do nothing. +In EE, the configuration extends this object to add a functioning squash-before-merge +button. +*/ + +export default { + template: '', +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js new file mode 100644 index 00000000000..f4ab2d9fa58 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js @@ -0,0 +1,27 @@ +export default { + name: 'MRWidgetUnresolvedDiscussions', + props: { + mr: { type: Object, required: true }, + }, + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge + </button> + <span class="bold"> + There are unresolved discussions. Please resolve these discussions + <span v-if="mr.canCreateIssue">or</span> + <span v-else>.</span> + </span> + <a + v-if="mr.createIssueToResolveDiscussionsPath" + :href="mr.createIssueToResolveDiscussionsPath" + class="btn btn-default btn-xs js-create-issue"> + Create an issue to resolve them later + </a> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js new file mode 100644 index 00000000000..cb02ffe93bd --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js @@ -0,0 +1,59 @@ +/* global Flash */ +import eventHub from '../../event_hub'; + +export default { + name: 'MRWidgetWIP', + props: { + mr: { type: Object, required: true }, + service: { type: Object, required: true }, + }, + data() { + return { + isMakingRequest: false, + }; + }, + methods: { + removeWIP() { + this.isMakingRequest = true; + this.service.removeWIP() + .then(res => res.json()) + .then((res) => { + eventHub.$emit('UpdateWidgetData', res); + new Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line + $('.merge-request .detail-page-description .title').text(this.mr.title); + }) + .catch(() => { + this.isMakingRequest = false; + new Flash('Something went wrong. Please try again.'); // eslint-disable-line + }); + }, + }, + template: ` + <div class="mr-widget-body"> + <button + type="button" + class="btn btn-success btn-small" + disabled="true"> + Merge</button> + <span class="bold"> + This merge request is currently Work In Progress and therefore unable to merge + </span> + <template v-if="mr.removeWIPPath"> + <i + class="fa fa-question-circle has-tooltip" + title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged." /> + <button + @click="removeWIP" + :disabled="isMakingRequest" + type="button" + class="btn btn-default btn-xs js-remove-wip"> + <i + v-if="isMakingRequest" + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + Resolve WIP status + </button> + </template> + </div> + `, +}; |