summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue457
1 files changed, 297 insertions, 160 deletions
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 2c1f2285dda..a890b176df0 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
@@ -9,13 +9,18 @@ import {
GlSprintf,
GlLink,
GlTooltipDirective,
+ GlSkeletonLoader,
} from '@gitlab/ui';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
+import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
import MergeRequest from '../../../merge_request';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import { deprecatedCreateFlash as Flash } from '../../../flash';
+import MergeRequestStore from '../../stores/mr_widget_store';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
import SquashBeforeMerge from './squash_before_merge.vue';
@@ -35,6 +40,31 @@ const MERGE_HOOK_VALIDATION_ERROR_STATUS = 'hook_validation_error';
export default {
name: 'ReadyToMerge',
+ apollo: {
+ state: {
+ query: readyToMergeQuery,
+ skip() {
+ return !this.glFeatures.mergeRequestWidgetGraphql;
+ },
+ variables() {
+ return this.mergeRequestQueryVariables;
+ },
+ manual: true,
+ result({ data }) {
+ this.state = {
+ ...data.project.mergeRequest,
+ mergeRequestsFfOnlyEnabled: data.mergeRequestsFfOnlyEnabled,
+ onlyAllowMergeIfPipelineSucceeds: data.onlyAllowMergeIfPipelineSucceeds,
+ };
+ this.removeSourceBranch = data.project.mergeRequest.shouldRemoveSourceBranch;
+ this.commitMessage = data.project.mergeRequest.defaultMergeCommitMessage;
+ this.squashBeforeMerge = data.project.mergeRequest.squashOnMerge;
+ this.isSquashReadOnly = data.project.squashReadOnly;
+ this.squashCommitMessage = data.project.mergeRequest.defaultSquashCommitMessage;
+ this.loading = false;
+ },
+ },
+ },
components: {
statusIcon,
SquashBeforeMerge,
@@ -48,6 +78,7 @@ export default {
GlButtonGroup,
GlDropdown,
GlDropdownItem,
+ GlSkeletonLoader,
MergeTrainHelperText: () =>
import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'),
MergeImmediatelyConfirmationDialog: () =>
@@ -58,13 +89,15 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [readyToMergeMixin],
+ mixins: [readyToMergeMixin, glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() {
return {
+ loading: this.glFeatures.mergeRequestWidgetGraphql,
+ state: {},
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
isMakingRequest: false,
isMergingImmediately: false,
@@ -75,13 +108,93 @@ export default {
};
},
computed: {
+ stateData() {
+ return this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr;
+ },
+ hasCI() {
+ return this.stateData.hasCI || this.stateData.hasCi;
+ },
isAutoMergeAvailable() {
- return !isEmpty(this.mr.availableAutoMergeStrategies);
+ return !isEmpty(this.stateData.availableAutoMergeStrategies);
+ },
+ pipeline() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.pipelines?.nodes?.[0];
+ }
+
+ return this.mr.pipeline;
+ },
+ isPipelineFailed() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return ['FAILED', 'CANCELED'].indexOf(this.pipeline?.status) !== -1;
+ }
+
+ return this.mr.isPipelineFailed;
+ },
+ isMergeAllowed() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.mergeable || false;
+ }
+
+ return this.mr.isMergeAllowed;
+ },
+ canRemoveSourceBranch() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.userPermissions.removeSourceBranch;
+ }
+
+ return this.mr.canRemoveSourceBranch;
+ },
+ commits() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.commitsWithoutMergeCommits.nodes;
+ }
+
+ return this.mr.commits;
+ },
+ commitsCount() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.commitCount || 0;
+ }
+
+ return this.mr.commitsCount;
+ },
+ preferredAutoMergeStrategy() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return MergeRequestStore.getPreferredAutoMergeStrategy(
+ this.state.availableAutoMergeStrategies,
+ );
+ }
+
+ return this.mr.preferredAutoMergeStrategy;
+ },
+ isSHAMismatch() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.mr.sha !== this.state.diffHeadSha;
+ }
+
+ return this.mr.isSHAMismatch;
+ },
+ squashIsSelected() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.squashReadOnly ? this.state.squashOnMerge : this.state.squash;
+ }
+
+ return this.mr.squashIsSelected;
+ },
+ isPipelineActive() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.pipeline?.active || false;
+ }
+
+ return this.mr.isPipelineActive;
},
status() {
- const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
+ const ciStatus = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.pipeline?.status.toLowerCase()
+ : this.mr.ciStatus;
- if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) {
+ if ((this.hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) {
return PIPELINE_FAILED_STATE;
}
@@ -89,7 +202,7 @@ export default {
return PIPELINE_PENDING_STATE;
}
- if (pipeline && isPipelineFailed) {
+ if (this.pipeline && this.isPipelineFailed) {
return PIPELINE_FAILED_STATE;
}
@@ -114,7 +227,7 @@ export default {
if (
this.status === PIPELINE_FAILED_STATE ||
!this.commitMessage.length ||
- !this.mr.isMergeAllowed ||
+ !this.isMergeAllowed ||
this.mr.preventMerge
) {
return WARNING;
@@ -133,27 +246,31 @@ export default {
return __('Merge');
},
hasPipelineMustSucceedConflict() {
- return !this.mr.hasCI && this.mr.onlyAllowMergeIfPipelineSucceeds;
+ return !this.hasCI && this.stateData.onlyAllowMergeIfPipelineSucceeds;
},
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
},
shouldShowSquashBeforeMerge() {
- const { commitsCount, enableSquashBeforeMerge, squashIsReadonly, squashIsSelected } = this.mr;
+ const { enableSquashBeforeMerge } = this.mr;
- if (squashIsReadonly && !squashIsSelected) {
+ if (this.isSquashReadOnly && !this.squashIsSelected) {
return false;
}
- return enableSquashBeforeMerge && commitsCount > 1;
+ return enableSquashBeforeMerge && this.commitsCount > 1;
},
shouldShowMergeControls() {
- return this.mr.isMergeAllowed || this.isAutoMergeAvailable;
+ return this.isMergeAllowed || this.isAutoMergeAvailable;
},
shouldShowSquashEdit() {
return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
},
shouldShowMergeEdit() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return !this.state.mergeRequestsFfOnlyEnabled;
+ }
+
return !this.mr.ffOnlyEnabled;
},
shaMismatchLink() {
@@ -162,18 +279,26 @@ export default {
},
methods: {
updateMergeCommitMessage(includeDescription) {
- const { commitMessageWithDescription, commitMessage } = this.mr;
+ const commitMessage = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.state.defaultMergeCommitMessage
+ : this.mr.commitMessage;
+ const commitMessageWithDescription = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.state.defaultMergeCommitMessageWithDescription
+ : this.mr.commitMessageWithDescription;
this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(useAutoMerge, mergeImmediately = false) {
if (mergeImmediately) {
this.isMergingImmediately = true;
}
+ const latestSha = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.state.diffHeadSha
+ : this.mr.latestSHA;
const options = {
- sha: this.mr.latestSHA || this.mr.sha,
+ sha: latestSha || this.mr.sha,
commit_message: this.commitMessage,
- auto_merge_strategy: useAutoMerge ? this.mr.preferredAutoMergeStrategy : undefined,
+ auto_merge_strategy: useAutoMerge ? this.preferredAutoMergeStrategy : undefined,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
};
@@ -188,8 +313,8 @@ export default {
this.isMakingRequest = true;
this.service
.merge(options)
- .then(res => res.data)
- .then(data => {
+ .then((res) => res.data)
+ .then((data) => {
const hasError =
data.status === MERGE_FAILED_STATUS ||
data.status === MERGE_HOOK_VALIDATION_ERROR_STATUS;
@@ -228,8 +353,8 @@ export default {
handleMergePolling(continuePolling, stopPolling) {
this.service
.poll()
- .then(res => res.data)
- .then(data => {
+ .then((res) => res.data)
+ .then((data) => {
if (data.state === 'merged') {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
@@ -270,8 +395,8 @@ export default {
handleRemoveBranchPolling(continuePolling, stopPolling) {
this.service
.poll()
- .then(res => res.data)
- .then(data => {
+ .then((res) => res.data)
+ .then((data) => {
// If source branch exists then we should continue polling
// because removing a source branch is a background task and takes time
if (data.source_branch_exists) {
@@ -294,156 +419,168 @@ export default {
<template>
<div>
- <div class="mr-widget-body media" :class="{ 'gl-pb-3': shouldRenderMergeTrainHelperText }">
- <status-icon :status="iconClass" />
- <div class="media-body">
- <div class="mr-widget-body-controls media space-children">
- <gl-button-group>
- <gl-button
- size="medium"
- category="primary"
- class="qa-merge-button accept-merge-request"
- :variant="mergeButtonVariant"
- :disabled="isMergeButtonDisabled"
- :loading="isMakingRequest"
- @click="handleMergeButtonClick(isAutoMergeAvailable)"
- >{{ mergeButtonText }}</gl-button
- >
- <gl-dropdown
- v-if="shouldShowMergeImmediatelyDropdown"
- v-gl-tooltip.hover.focus="__('Select merge moment')"
- :disabled="isMergeButtonDisabled"
- variant="info"
- data-qa-selector="merge_moment_dropdown"
- toggle-class="btn-icon js-merge-moment"
- >
- <template #button-content>
- <gl-icon name="chevron-down" class="mr-0" />
- <span class="sr-only">{{ __('Select merge moment') }}</span>
- </template>
- <gl-dropdown-item
- icon-name="warning"
- button-class="accept-merge-request js-merge-immediately-button"
- data-qa-selector="merge_immediately_option"
- @click="handleMergeImmediatelyButtonClick"
+ <div v-if="loading" class="mr-widget-body">
+ <div class="gl-w-full mr-ready-to-merge-loader">
+ <gl-skeleton-loader :width="418" :height="30">
+ <rect x="0" y="3" width="24" height="24" rx="4" />
+ <rect x="32" y="0" width="70" height="30" rx="4" />
+ <rect x="110" y="7" width="150" height="16" rx="4" />
+ <rect x="268" y="7" width="150" height="16" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ </div>
+ <template v-else>
+ <div class="mr-widget-body media" :class="{ 'gl-pb-3': shouldRenderMergeTrainHelperText }">
+ <status-icon :status="iconClass" />
+ <div class="media-body">
+ <div class="mr-widget-body-controls media space-children">
+ <gl-button-group>
+ <gl-button
+ size="medium"
+ category="primary"
+ class="qa-merge-button accept-merge-request"
+ :variant="mergeButtonVariant"
+ :disabled="isMergeButtonDisabled"
+ :loading="isMakingRequest"
+ @click="handleMergeButtonClick(isAutoMergeAvailable)"
+ >{{ mergeButtonText }}</gl-button
>
- {{ __('Merge immediately') }}
- </gl-dropdown-item>
- <merge-immediately-confirmation-dialog
- ref="confirmationDialog"
- :docs-url="mr.mergeImmediatelyDocsPath"
- @mergeImmediately="onMergeImmediatelyConfirmation"
- />
- </gl-dropdown>
- </gl-button-group>
- <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"
- class="js-remove-source-branch-checkbox"
- type="checkbox"
+ <gl-dropdown
+ v-if="shouldShowMergeImmediatelyDropdown"
+ v-gl-tooltip.hover.focus="__('Select merge moment')"
+ :disabled="isMergeButtonDisabled"
+ variant="info"
+ data-qa-selector="merge_moment_dropdown"
+ toggle-class="btn-icon js-merge-moment"
+ >
+ <template #button-content>
+ <gl-icon name="chevron-down" class="mr-0" />
+ <span class="sr-only">{{ __('Select merge moment') }}</span>
+ </template>
+ <gl-dropdown-item
+ icon-name="warning"
+ button-class="accept-merge-request js-merge-immediately-button"
+ data-qa-selector="merge_immediately_option"
+ @click="handleMergeImmediatelyButtonClick"
+ >
+ {{ __('Merge immediately') }}
+ </gl-dropdown-item>
+ <merge-immediately-confirmation-dialog
+ ref="confirmationDialog"
+ :docs-url="mr.mergeImmediatelyDocsPath"
+ @mergeImmediately="onMergeImmediatelyConfirmation"
/>
- {{ __('Delete source branch') }}
- </label>
+ </gl-dropdown>
+ </gl-button-group>
+ <div class="media-body-wrap space-children">
+ <template v-if="shouldShowMergeControls">
+ <label v-if="canRemoveSourceBranch">
+ <input
+ id="remove-source-branch-input"
+ v-model="removeSourceBranch"
+ :disabled="isRemoveSourceBranchButtonDisabled"
+ class="js-remove-source-branch-checkbox"
+ type="checkbox"
+ />
+ {{ __('Delete source branch') }}
+ </label>
- <!-- Placeholder for EE extension of this component -->
- <squash-before-merge
- v-if="shouldShowSquashBeforeMerge"
- v-model="squashBeforeMerge"
- :help-path="mr.squashBeforeMergeHelpPath"
- :is-disabled="isSquashReadOnly"
- />
- </template>
- <template v-else>
- <div class="bold js-resolve-mr-widget-items-message">
- <div
- v-if="hasPipelineMustSucceedConflict"
- class="gl-display-flex gl-align-items-center"
- data-testid="pipeline-succeed-conflict"
- >
- <gl-sprintf :message="pipelineMustSucceedConflictText" />
- <gl-link
- :href="mr.pipelineMustSucceedDocsPath"
- target="_blank"
- class="gl-display-flex gl-ml-2"
+ <!-- Placeholder for EE extension of this component -->
+ <squash-before-merge
+ v-if="shouldShowSquashBeforeMerge"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isSquashReadOnly"
+ />
+ </template>
+ <template v-else>
+ <div class="bold js-resolve-mr-widget-items-message">
+ <div
+ v-if="hasPipelineMustSucceedConflict"
+ class="gl-display-flex gl-align-items-center"
+ data-testid="pipeline-succeed-conflict"
>
- <gl-icon name="question" />
- </gl-link>
+ <gl-sprintf :message="pipelineMustSucceedConflictText" />
+ <gl-link
+ :href="mr.pipelineMustSucceedDocsPath"
+ target="_blank"
+ class="gl-display-flex gl-ml-2"
+ >
+ <gl-icon name="question" />
+ </gl-link>
+ </div>
+ <gl-sprintf v-else :message="mergeDisabledText" />
</div>
- <gl-sprintf v-else :message="mergeDisabledText" />
- </div>
- </template>
- </div>
- </div>
- <div v-if="mr.isSHAMismatch" class="d-flex align-items-center mt-2 js-sha-mismatch">
- <gl-icon name="warning-solid" class="text-warning mr-1" />
- <span class="text-warning">
- <gl-sprintf
- :message="
- __('New changes were added. %{linkStart}Reload the page to review them%{linkEnd}')
- "
- >
- <template #link="{ content }">
- <gl-link :href="mr.mergeRequestDiffsPath">{{ content }}</gl-link>
</template>
- </gl-sprintf>
- </span>
+ </div>
+ </div>
+ <div v-if="isSHAMismatch" class="d-flex align-items-center mt-2 js-sha-mismatch">
+ <gl-icon name="warning-solid" class="text-warning mr-1" />
+ <span class="text-warning">
+ <gl-sprintf
+ :message="
+ __('New changes were added. %{linkStart}Reload the page to review them%{linkEnd}')
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="mr.mergeRequestDiffsPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </div>
</div>
</div>
- </div>
- <merge-train-helper-text
- v-if="shouldRenderMergeTrainHelperText"
- :pipeline-id="mr.pipeline.id"
- :pipeline-link="mr.pipeline.path"
- :merge-train-length="mr.mergeTrainsCount"
- :merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
- />
- <template v-if="shouldShowMergeControls">
- <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
- {{ __('Fast-forward merge without a merge commit') }}
- </div>
- <commits-header
- v-if="shouldShowSquashEdit || shouldShowMergeEdit"
- :is-squash-enabled="squashBeforeMerge"
- :commits-count="mr.commitsCount"
- :target-branch="mr.targetBranch"
- :is-fast-forward-enabled="mr.ffOnlyEnabled"
- :class="{ 'border-bottom': mr.mergeError }"
- >
- <ul class="border-top content-list commits-list flex-list">
- <commit-edit
- v-if="shouldShowSquashEdit"
- v-model="squashCommitMessage"
- :label="__('Squash commit message')"
- input-id="squash-message-edit"
- squash
- >
- <commit-message-dropdown
- slot="header"
+ <merge-train-helper-text
+ v-if="shouldRenderMergeTrainHelperText"
+ :pipeline-id="pipeline.id"
+ :pipeline-link="pipeline.path"
+ :merge-train-length="stateData.mergeTrainsCount"
+ :merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
+ />
+ <template v-if="shouldShowMergeControls">
+ <div v-if="!shouldShowMergeEdit" class="mr-fast-forward-message">
+ {{ __('Fast-forward merge without a merge commit') }}
+ </div>
+ <commits-header
+ v-if="shouldShowSquashEdit || shouldShowMergeEdit"
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="commitsCount"
+ :target-branch="stateData.targetBranch"
+ :is-fast-forward-enabled="!shouldShowMergeEdit"
+ :class="{ 'border-bottom': stateData.mergeError }"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="shouldShowSquashEdit"
v-model="squashCommitMessage"
- :commits="mr.commits"
- />
- </commit-edit>
- <commit-edit
- v-if="shouldShowMergeEdit"
- v-model="commitMessage"
- :label="__('Merge commit message')"
- input-id="merge-message-edit"
- >
- <label slot="checkbox">
- <input
- id="include-description"
- type="checkbox"
- @change="updateMergeCommitMessage($event.target.checked)"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
+ v-model="squashCommitMessage"
+ :commits="commits"
/>
- {{ __('Include merge request description') }}
- </label>
- </commit-edit>
- </ul>
- </commits-header>
+ </commit-edit>
+ <commit-edit
+ v-if="shouldShowMergeEdit"
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
+ />
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
+ </template>
</template>
</div>
</template>