diff options
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.vue | 457 |
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> |