diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
40 files changed, 707 insertions, 310 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue index 7297f8f8677..9b822657184 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue @@ -74,7 +74,7 @@ export default { return this.mr.approvals || {}; }, approvedBy() { - return this.approvals.approved_by ? this.approvals.approved_by.map(x => x.user) : []; + return this.approvals.approved_by ? this.approvals.approved_by.map((x) => x.user) : []; }, userHasApproved() { return Boolean(this.approvals.user_has_approved); @@ -136,7 +136,7 @@ export default { approveWithAuth(data) { this.updateApproval( () => this.service.approveMergeRequestWithAuth(data), - error => { + (error) => { if (error && error.response && error.response.status === 401) { this.hasApprovalAuthError = true; return; @@ -155,7 +155,7 @@ export default { this.isApproving = true; this.clearError(); return serviceFn() - .then(data => { + .then((data) => { this.mr.setApprovals(data); eventHub.$emit('MRWidgetUpdateRequested'); this.$emit('updated'); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue index b90cbfd1a1a..215df8acece 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue @@ -121,7 +121,7 @@ export default { this.actionInProgress = actionName; MRWidgetService.executeInlineAction(endpoint) - .then(resp => { + .then((resp) => { const redirectUrl = resp?.data?.redirect_url; if (redirectUrl) { visitUrl(redirectUrl); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue index e3c0b7935d7..abc831c8abe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue @@ -57,7 +57,7 @@ export default { return this.deployment.changes && this.deployment.changes.length > 1; }, filteredChanges() { - return this.deployment?.changes?.filter(change => change.path.includes(this.searchTerm)); + return this.deployment?.changes?.filter((change) => change.path.includes(this.searchTerm)); }, }, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue index 9b2cd41092e..2f27216f2e9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue @@ -102,7 +102,7 @@ export default { loadMetrics() { backOff((next, stop) => { MRWidgetService.fetchMetrics(this.metricsUrl) - .then(res => { + .then((res) => { if (res.status === statusCodes.NO_CONTENT) { this.backOffRequestCounter += 1; /* eslint-disable no-unused-expressions */ @@ -113,14 +113,14 @@ export default { }) .catch(stop); }) - .then(res => { + .then((res) => { if (res.status === statusCodes.NO_CONTENT) { return res; } return res.data; }) - .then(data => { + .then((data) => { this.computeGraphData(data.metrics, data.deployment_time); return data; }) @@ -145,7 +145,7 @@ export default { <template #metricsLink="{ content }"> <gl-link :href="metricsMonitoringUrl">{{ content }}</gl-link> </template> - <template #emphasis="{content}"> + <template #emphasis="{ content }"> <strong>{{ content }}</strong> </template> <template #memoryFrom>{{ memoryFrom }}</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index eff26729fa7..33809b953ee 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -67,11 +67,11 @@ export default { }, mounted() { this.fetchCollapsedData(this.$props) - .then(data => { + .then((data) => { this.collapsedData = data; this.loadingState = null; }) - .catch(e => { + .catch((e) => { this.loadingState = LOADING_STATES.collapsedError; throw e; }); @@ -86,11 +86,11 @@ export default { this.loadingState = LOADING_STATES.expandedLoading; this.fetchFullData(this.$props) - .then(data => { + .then((data) => { this.loadingState = null; this.fullData = data; }) - .catch(e => { + .catch((e) => { this.loadingState = null; throw e; }); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js index 5014c12dc30..529160de6a7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js @@ -11,7 +11,7 @@ export default { return h( 'div', {}, - extensions.map(extension => + extensions.map((extension) => h(extension, { props: extensions[0].props.reduce( (acc, key) => ({ diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js index 2bfaec8a1c9..9796bb44939 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js @@ -3,7 +3,7 @@ import ExtensionBase from './base.vue'; // Holds all the currently registered extensions export const extensions = []; -export const registerExtension = extension => { +export const registerExtension = (extension) => { // Pushes into the extenions array a dynamically created Vue component // that gets exteneded from `base.vue` extensions.push({ diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue index a2636ce52ad..560a68031ef 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue @@ -13,7 +13,7 @@ export default { type: String, required: false, default: DANGER, - validator: value => [WARNING, DANGER].includes(value), + validator: (value) => [WARNING, DANGER].includes(value), }, helpPath: { type: String, @@ -36,7 +36,7 @@ export default { </script> <template> - <div class="m-3 ml-7" :class="messageClass"> + <div class="gl-m-3 gl-ml-7" :class="messageClass"> <slot></slot> <gl-link v-if="helpPath" :href="helpPath" target="_blank"> <gl-icon :size="16" name="question-o" class="align-middle" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue index 1727383ea2c..3cd003461b3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue @@ -30,7 +30,7 @@ export default { }; </script> <template> - <section class="mr-widget-help font-italic"> + <section class="gl-py-3 gl-pr-3 gl-pl-5 gl-ml-7 mr-widget-help gl-font-style-italic"> <template v-if="missingBranch"> {{ missingBranchInfo }} </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 5066a88b52b..4c130945487 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -11,6 +11,7 @@ import { import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; import { s__, n__ } from '~/locale'; import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue'; +import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; @@ -23,6 +24,7 @@ export default { GlIcon, GlSprintf, GlTooltip, + PipelineArtifacts, PipelineStage, TooltipOnTruncate, LinkedPipelinesMiniList: () => @@ -97,6 +99,9 @@ export default { hasCommitInfo() { return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0; }, + hasArtifacts() { + return this.pipeline?.details?.artifacts?.length > 0; + }, isMergeRequestPipeline() { return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline); }, @@ -142,7 +147,7 @@ export default { data-testid="ci-error-message" > <gl-sprintf :message="$options.errorText"> - <template #link="{content}"> + <template #link="{ content }"> <gl-link :href="mrTroubleshootingDocsPath">{{ content }}</gl-link> </template> </gl-sprintf> @@ -218,7 +223,6 @@ export default { data-testid="pipeline-coverage-delta" >({{ pipelineCoverageDelta }}%)</span > - {{ pipelineCoverageJobNumberText }} <span ref="pipelineCoverageQuestion"> <gl-icon name="question" :size="12" /> @@ -258,6 +262,11 @@ export default { </template> </span> <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" /> + <pipeline-artifacts + v-if="hasArtifacts" + :artifacts="pipeline.details.artifacts" + class="gl-ml-3" + /> </span> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue index 56a50b55f9d..43317130b08 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -30,7 +30,7 @@ export default { }; </script> <template> - <section class="mr-info-list mr-links"> + <section class="mr-info-list gl-ml-7 gl-pb-5"> <p v-if="relatedLinks.closing">{{ closesText }} <span v-html="relatedLinks.closing"></span></p> <p v-if="relatedLinks.mentioned"> {{ s__('mrWidget|Mentions') }} <span v-html="relatedLinks.mentioned"></span> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue index a9d148505e1..7acdd695cc2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue @@ -86,12 +86,12 @@ export default { to create one.`) " > - <template #prefixToLink="{content}"> + <template #prefixToLink="{ content }"> <strong> {{ content }} </strong> </template> - <template #addPipelineLink="{content}"> + <template #addPipelineLink="{ content }"> <gl-link :href="pipelinePath" class="gl-ml-1" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue index c917b69953f..d50d97e3570 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue @@ -18,7 +18,7 @@ export default { </script> <template> - <p v-once class="mr-info-list mr-links gl-mb-0"> + <p v-once class="mr-info-list gl-ml-7 gl-pb-5 gl-mb-0"> <span class="status-text"> <gl-sprintf :message="$options.i18n.removesBranchText"> <template #strong="{ content }"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue index b6722de5277..73d75352cb5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue @@ -25,12 +25,13 @@ export default { class="mr-commit-dropdown" > <gl-dropdown-item - v-for="commit in commits" - :key="commit.short_id" + v-for="(commit, index) in commits" + :key="index" class="text-nowrap text-truncate" @click="$emit('input', commit.message)" > - <span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }} + <span class="monospace mr-2">{{ commit.shortId || commit.short_id }}</span> + {{ commit.title }} </gl-dropdown-item> </gl-dropdown> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue index bdcea9871ea..d331f1690f5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue @@ -83,6 +83,7 @@ export default { :aria-label="ariaLabel" category="tertiary" class="commit-edit-toggle gl-mr-3" + size="small" :icon="collapseIcon" @click.stop="toggle()" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index 750014c599a..20ac8f5a467 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -1,21 +1,37 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui'; import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge'; +import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { deprecatedCreateFlash as Flash } from '../../../flash'; import statusIcon from '../mr_widget_status_icon.vue'; import MrWidgetAuthor from '../mr_widget_author.vue'; import eventHub from '../../event_hub'; import { AUTO_MERGE_STRATEGIES } from '../../constants'; import { __ } from '~/locale'; +import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; export default { name: 'MRWidgetAutoMergeEnabled', + apollo: { + state: { + query: autoMergeEnabledQuery, + skip() { + return !this.glFeatures.mergeRequestWidgetGraphql; + }, + variables() { + return this.mergeRequestQueryVariables; + }, + update: (data) => data.project?.mergeRequest, + }, + }, components: { MrWidgetAuthor, statusIcon, GlLoadingIcon, + GlSkeletonLoader, }, - mixins: [autoMergeMixin], + mixins: [autoMergeMixin, glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], props: { mr: { type: Object, @@ -30,20 +46,47 @@ export default { }, data() { return { + state: {}, isCancellingAutoMerge: false, isRemovingSourceBranch: false, }; }, computed: { + loading() { + return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading; + }, + mergeUser() { + if (this.glFeatures.mergeRequestWidgetGraphql) { + return this.state.mergeUser; + } + + return this.mr.setToAutoMergeBy; + }, + targetBranch() { + return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).targetBranch; + }, + shouldRemoveSourceBranch() { + if (this.glFeatures.mergeRequestWidgetGraphql) { + return this.state.shouldRemoveSourceBranch || this.state.forceRemoveSourceBranch; + } + + return this.mr.shouldRemoveSourceBranch; + }, + autoMergeStrategy() { + return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).autoMergeStrategy; + }, canRemoveSourceBranch() { - const { - shouldRemoveSourceBranch, - canRemoveSourceBranch, - mergeUserId, - currentUserId, - } = this.mr; + const { currentUserId } = this.mr; + const mergeUserId = this.glFeatures.mergeRequestWidgetGraphql + ? this.state.mergeUser?.id + : this.mr.mergeUserId; + const canRemoveSourceBranch = this.glFeatures.mergeRequestWidgetGraphql + ? this.state.userPermissions.removeSourceBranch + : this.mr.canRemoveSourceBranch; - return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId; + return ( + !this.shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId + ); }, }, methods: { @@ -51,8 +94,8 @@ export default { this.isCancellingAutoMerge = true; this.service .cancelAutomaticMerge() - .then(res => res.data) - .then(data => { + .then((res) => res.data) + .then((data) => { eventHub.$emit('UpdateWidgetData', data); }) .catch(() => { @@ -63,15 +106,15 @@ export default { removeSourceBranch() { const options = { sha: this.mr.sha, - auto_merge_strategy: this.mr.autoMergeStrategy, + auto_merge_strategy: this.autoMergeStrategy, should_remove_source_branch: true, }; this.isRemovingSourceBranch = true; this.service .merge(options) - .then(res => res.data) - .then(data => { + .then((res) => res.data) + .then((data) => { if (AUTO_MERGE_STRATEGIES.includes(data.status)) { eventHub.$emit('MRWidgetUpdateRequested'); } @@ -86,49 +129,64 @@ export default { </script> <template> <div class="mr-widget-body media"> - <status-icon status="success" /> - <div class="media-body"> - <h4 class="d-flex align-items-start"> - <span class="gl-mr-3"> - <span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span> - <mr-widget-author :author="mr.setToAutoMergeBy" /> - <span class="js-status-text-after-author">{{ statusTextAfterAuthor }}</span> - </span> - <a - v-if="mr.canCancelAutomaticMerge" - :disabled="isCancellingAutoMerge" - role="button" - href="#" - class="btn btn-sm btn-default js-cancel-auto-merge" - @click.prevent="cancelAutomaticMerge" - > - <gl-loading-icon v-if="isCancellingAutoMerge" inline class="gl-mr-1" /> - {{ cancelButtonText }} - </a> - </h4> - <section class="mr-info-list"> - <p> - {{ s__('mrWidget|The changes will be merged into') }} - <a :href="mr.targetBranchPath" class="label-branch">{{ mr.targetBranch }}</a> - </p> - <p v-if="mr.shouldRemoveSourceBranch"> - {{ s__('mrWidget|The source branch will be deleted') }} - </p> - <p v-else class="d-flex align-items-start"> - <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span> + <div v-if="loading" class="gl-w-full mr-conflict-loader"> + <gl-skeleton-loader :width="334" :height="30"> + <rect x="0" y="3" width="24" height="24" rx="4" /> + <rect x="32" y="7" width="150" height="16" rx="4" /> + <rect x="190" y="7" width="144" height="16" rx="4" /> + </gl-skeleton-loader> + </div> + <template v-else> + <status-icon status="success" /> + <div class="media-body"> + <h4 class="gl-display-flex"> + <span class="gl-mr-3"> + <span class="js-status-text-before-author" data-testid="beforeStatusText">{{ + statusTextBeforeAuthor + }}</span> + <mr-widget-author :author="mergeUser" /> + <span class="js-status-text-after-author" data-testid="afterStatusText">{{ + statusTextAfterAuthor + }}</span> + </span> <a - v-if="canRemoveSourceBranch" - :disabled="isRemovingSourceBranch" + v-if="mr.canCancelAutomaticMerge" + :disabled="isCancellingAutoMerge" role="button" - class="btn btn-sm btn-default js-remove-source-branch" href="#" - @click.prevent="removeSourceBranch" + class="btn btn-sm btn-default js-cancel-auto-merge" + data-testid="cancelAutomaticMergeButton" + @click.prevent="cancelAutomaticMerge" > - <gl-loading-icon v-if="isRemovingSourceBranch" inline class="gl-mr-1" /> - {{ s__('mrWidget|Delete source branch') }} + <gl-loading-icon v-if="isCancellingAutoMerge" inline class="gl-mr-1" /> + {{ cancelButtonText }} </a> - </p> - </section> - </div> + </h4> + <section class="mr-info-list"> + <p> + {{ s__('mrWidget|The changes will be merged into') }} + <a :href="mr.targetBranchPath" class="label-branch">{{ targetBranch }}</a> + </p> + <p v-if="shouldRemoveSourceBranch"> + {{ s__('mrWidget|The source branch will be deleted') }} + </p> + <p v-else class="gl-display-flex"> + <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span> + <a + v-if="canRemoveSourceBranch" + :disabled="isRemovingSourceBranch" + role="button" + class="btn btn-sm btn-default js-remove-source-branch" + href="#" + data-testid="removeSourceBranchButton" + @click.prevent="removeSourceBranch" + > + <gl-loading-icon v-if="isRemovingSourceBranch" inline class="gl-mr-1" /> + {{ s__('mrWidget|Delete source branch') }} + </a> + </p> + </section> + </div> + </template> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index 30da9947859..a2771bc4bfb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -1,7 +1,10 @@ <script> import { GlLoadingIcon, GlButton } from '@gitlab/ui'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; +import autoMergeFailedQuery from '../../queries/states/auto_merge_failed.query.graphql'; +import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; export default { name: 'MRWidgetAutoMergeFailed', @@ -10,6 +13,19 @@ export default { GlLoadingIcon, GlButton, }, + mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], + apollo: { + mergeError: { + query: autoMergeFailedQuery, + skip() { + return !this.glFeatures.mergeRequestWidgetGraphql; + }, + variables() { + return this.mergeRequestQueryVariables; + }, + update: (data) => data.project?.mergeRequest?.mergeError, + }, + }, props: { mr: { type: Object, @@ -18,6 +34,7 @@ export default { }, data() { return { + mergeError: this.glFeatures.mergeRequestWidgetGraphql ? null : this.mr.mergeError, isRefreshing: false, }; }, @@ -36,7 +53,7 @@ export default { <status-icon status="warning" /> <div class="media-body space-children gl-display-flex gl-flex-wrap gl-align-items-center"> <span class="bold"> - <template v-if="mr.mergeError">{{ mr.mergeError }}</template> + <template v-if="mergeError">{{ mergeError }}</template> {{ s__('mrWidget|This merge request failed to be merged automatically') }} </span> <gl-button diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue index 87c59e5ece9..3d5daa4979b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue @@ -30,7 +30,7 @@ export default { variables() { return this.mergeRequestQueryVariables; }, - update: data => data.project.mergeRequest.userPermissions, + update: (data) => data.project.mergeRequest.userPermissions, }, stateData: { query: conflictsStateQuery, @@ -40,7 +40,7 @@ export default { variables() { return this.mergeRequestQueryVariables; }, - update: data => data.project.mergeRequest, + update: (data) => data.project.mergeRequest, }, }, props: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 17cd740ddd9..9d646dbfb3e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -83,8 +83,8 @@ export default { this.service .removeSourceBranch() - .then(res => res.data) - .then(data => { + .then((res) => res.data) + .then((data) => { // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 // eslint-disable-next-line @gitlab/require-i18n-strings if (data.message === 'Branch was deleted') { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue index 8511797286d..3f68979bc0e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue @@ -25,7 +25,7 @@ export default { variables() { return this.mergeRequestQueryVariables; }, - update: data => data.project.mergeRequest, + update: (data) => data.project.mergeRequest, }, }, props: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index 14c2e9fa828..bf86e0d8b07 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -1,19 +1,36 @@ <script> /* eslint-disable vue/no-v-html */ -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlSkeletonLoader } from '@gitlab/ui'; import { escape } from 'lodash'; +import { __, sprintf } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import simplePoll from '../../../lib/utils/simple_poll'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; +import rebaseQuery from '../../queries/states/ready_to_merge.query.graphql'; +import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import { deprecatedCreateFlash as Flash } from '../../../flash'; -import { __, sprintf } from '~/locale'; export default { name: 'MRWidgetRebase', + apollo: { + state: { + query: rebaseQuery, + skip() { + return !this.glFeatures.mergeRequestWidgetGraphql; + }, + variables() { + return this.mergeRequestQueryVariables; + }, + update: (data) => data.project.mergeRequest, + }, + }, components: { statusIcon, GlButton, + GlSkeletonLoader, }, + mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], props: { mr: { type: Object, @@ -26,16 +43,41 @@ export default { }, data() { return { + state: {}, isMakingRequest: false, rebasingError: null, }; }, computed: { + isLoading() { + return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading; + }, + rebaseInProgress() { + if (this.glFeatures.mergeRequestWidgetGraphql) { + return this.state.rebaseInProgress; + } + + return this.mr.rebaseInProgress; + }, + canPushToSourceBranch() { + if (this.glFeatures.mergeRequestWidgetGraphql) { + return this.state.userPermissions.pushToSourceBranch; + } + + return this.mr.canPushToSourceBranch; + }, + targetBranch() { + if (this.glFeatures.mergeRequestWidgetGraphql) { + return this.state.targetBranch; + } + + return this.mr.targetBranch; + }, status() { - if (this.mr.rebaseInProgress || this.isMakingRequest) { + if (this.rebaseInProgress || this.isMakingRequest) { return 'loading'; } - if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) { + if (!this.canPushToSourceBranch && !this.rebaseInProgress) { return 'warning'; } return 'success'; @@ -49,7 +91,7 @@ export default { 'Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged.', ), { - targetBranch: `<span class="label-branch">${escape(this.mr.targetBranch)}</span>`, + targetBranch: `<span class="label-branch">${escape(this.targetBranch)}</span>`, }, false, ); @@ -65,7 +107,7 @@ export default { .then(() => { simplePoll(this.checkRebaseStatus); }) - .catch(error => { + .catch((error) => { this.isMakingRequest = false; if (error.response && error.response.data && error.response.data.merge_error) { @@ -78,8 +120,8 @@ export default { checkRebaseStatus(continuePolling, stopPolling) { this.service .poll() - .then(res => res.data) - .then(res => { + .then((res) => res.data) + .then((res) => { if (res.rebase_in_progress) { continuePolling(); } else { @@ -105,17 +147,30 @@ export default { </script> <template> <div class="mr-widget-body media"> - <status-icon :status="status" :show-disabled-button="showDisabledButton" /> + <div v-if="isLoading" class="gl-w-full mr-conflict-loader"> + <gl-skeleton-loader :width="334" :height="30"> + <rect x="0" y="3" width="24" height="24" rx="4" /> + <rect x="32" y="5" width="302" height="20" rx="4" /> + </gl-skeleton-loader> + </div> + <template v-else> + <status-icon :status="status" :show-disabled-button="showDisabledButton" /> - <div class="rebase-state-find-class-convention media media-body space-children"> - <template v-if="mr.rebaseInProgress || isMakingRequest"> - <span class="bold" data-testid="rebase-message">{{ __('Rebase in progress') }}</span> - </template> - <template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch"> - <span class="bold" data-testid="rebase-message" v-html="fastForwardMergeText"></span> - </template> - <template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest"> + <div class="rebase-state-find-class-convention media media-body space-children"> + <span + v-if="rebaseInProgress || isMakingRequest" + class="gl-font-weight-bold gl-ml-0!" + data-testid="rebase-message" + >{{ __('Rebase in progress') }}</span + > + <span + v-if="!rebaseInProgress && !canPushToSourceBranch" + class="gl-font-weight-bold gl-ml-0!" + data-testid="rebase-message" + v-html="fastForwardMergeText" + ></span> <div + v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest" class="accept-merge-holder clearfix js-toggle-container accept-action media space-children" > <gl-button @@ -126,14 +181,21 @@ export default { > {{ __('Rebase') }} </gl-button> - <span v-if="!rebasingError" class="bold" data-testid="rebase-message">{{ - __( - 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.', - ) + <span + v-if="!rebasingError" + class="gl-font-weight-bold gl-ml-0!" + data-testid="rebase-message" + >{{ + __( + 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.', + ) + }}</span + > + <span v-else class="gl-font-weight-bold danger gl-ml-0!" data-testid="rebase-message">{{ + rebasingError }}</span> - <span v-else class="bold danger" data-testid="rebase-message">{{ rebasingError }}</span> </div> - </template> - </div> + </div> + </template> </div> </template> 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> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue index 1c9909e7178..12fdfe601a4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue @@ -46,7 +46,7 @@ export default { name="squash" class="qa-squash-checkbox js-squash-checkbox gl-mb-0 gl-mr-2" :title="tooltipTitle" - @change="checked => $emit('input', checked)" + @change="(checked) => $emit('input', checked)" > {{ $options.i18n.checkboxLabel }} </gl-form-checkbox> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index 1d591168a17..3f1db815f95 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -29,7 +29,7 @@ export default { variables() { return this.mergeRequestQueryVariables; }, - update: data => data.project.mergeRequest.userPermissions, + update: (data) => data.project.mergeRequest.userPermissions, }, }, props: { @@ -86,7 +86,7 @@ export default { variables: mergeRequestQueryVariables, }); - const data = produce(sourceData, draftState => { + const data = produce(sourceData, (draftState) => { // eslint-disable-next-line no-param-reassign draftState.project.mergeRequest.workInProgress = workInProgress; // eslint-disable-next-line no-param-reassign @@ -113,10 +113,18 @@ export default { }, }, }) - .then(({ data: { mergeRequestSetWip: { mergeRequest: { title } } } }) => { - createFlash(__('The merge request can now be merged.'), 'notice'); - $('.merge-request .detail-page-description .title').text(title); - }) + .then( + ({ + data: { + mergeRequestSetWip: { + mergeRequest: { title }, + }, + }, + }) => { + createFlash(__('The merge request can now be merged.'), 'notice'); + $('.merge-request .detail-page-description .title').text(title); + }, + ) .catch(() => createFlash(__('Something went wrong. Please try again.'))) .finally(() => { this.isMakingRequest = false; @@ -129,8 +137,8 @@ export default { this.isMakingRequest = true; this.service .removeWIP() - .then(res => res.data) - .then(data => { + .then((res) => res.data) + .then((data) => { eventHub.$emit('UpdateWidgetData', data); MergeRequest.toggleDraftStatus(this.mr.title, true); }) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue index 4de41dd5887..180db7828a8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue @@ -40,7 +40,7 @@ export default { ); }, numberOfInvalidPlans() { - return Object.values(this.plansObject).filter(plan => plan.tf_report_error).length; + return Object.values(this.plansObject).filter((plan) => plan.tf_report_error).length; }, numberOfPlans() { return Object.keys(this.plansObject).length; diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js index 2d21ced1b28..6c6f5e7fc73 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js @@ -38,7 +38,7 @@ export default { .query({ query: issuesQuery, variables: { projectPath: targetProjectFullPath } }) .then(({ data }) => { // Return some transformed data to be rendered in the expanded state - return data.project.issues.nodes.map(issue => ({ + return data.project.issues.nodes.map((issue) => ({ id: issue.id, // Required: The ID of the object text: issue.title, // Required: The text to get used on each row // Icon to get rendered on the side of each row diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index 8f2cca3309a..d512877a20d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -26,7 +26,11 @@ export default () => { registerExtension(issueExtension); - const vm = new Vue({ ...MrWidgetOptions, apolloProvider }); + const vm = new Vue({ + el: '#js-vue-mr-widget', + ...MrWidgetOptions, + apolloProvider, + }); window.gl.mrWidget = { checkStatus: vm.checkStatus, diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js index e50555ca875..943011949fd 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js @@ -11,7 +11,7 @@ export default { } }, refreshApprovals() { - return this.service.fetchApprovals().then(data => { + return this.service.fetchApprovals().then((data) => { this.mr.setApprovals(data); }); }, diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js index dc8a6b56d58..fe512d68ea2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js @@ -27,7 +27,7 @@ export default { return __('Merge when pipeline succeeds'); }, shouldShowMergeImmediatelyDropdown() { - return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds; + return this.isPipelineActive && !this.stateData.onlyAllowMergeIfPipelineSucceeds; }, isMergeImmediatelyDangerous() { return false; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 3f1f2144d8e..519576d9fe6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -43,12 +43,11 @@ import SourceBranchRemovalStatus from './components/source_branch_removal_status import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue'; import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue'; import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue'; -import { setFaviconOverlay } from '../lib/utils/common_utils'; +import { setFaviconOverlay } from '../lib/utils/favicon'; import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue'; import getStateQuery from './queries/get_state.query.graphql'; export default { - el: '#js-vue-mr-widget', // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 // eslint-disable-next-line @gitlab/require-i18n-strings name: 'MRWidget', @@ -166,7 +165,8 @@ export default { return ( !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch && - (!this.mr.isNothingToMergeState && !this.mr.isMergedState) + !this.mr.isNothingToMergeState && + !this.mr.isMergedState ); }, shouldRenderCollaborationStatus() { @@ -190,9 +190,13 @@ export default { mergeError = mergeError.slice(0, -1); } - return sprintf(s__('mrWidget|Merge failed: %{mergeError}. Please try again.'), { - mergeError, - }); + return sprintf( + s__('mrWidget|Merge failed: %{mergeError}. Please try again.'), + { + mergeError, + }, + false, + ); }, shouldShowAccessibilityReport() { return this.mr.accessibilityReportPath; @@ -306,8 +310,7 @@ export default { callback: this.checkStatus, startingInterval: this.startingPollInterval, maxInterval: this.startingPollInterval + secondsToMilliseconds(4 * 60), - hiddenInterval: - window.gon?.features?.widgetVisibilityPolling && secondsToMilliseconds(6 * 60), + hiddenInterval: secondsToMilliseconds(6 * 60), incrementByFactorOf: 2, }); }, @@ -357,7 +360,7 @@ export default { fetchActionsContent() { this.service .fetchMergeActionsContent() - .then(res => { + .then((res) => { if (res.data) { const el = document.createElement('div'); el.innerHTML = res.data; @@ -387,26 +390,26 @@ export default { this.pollingInterval.stopTimer(); }, bindEventHubListeners() { - eventHub.$on('MRWidgetUpdateRequested', cb => { + eventHub.$on('MRWidgetUpdateRequested', (cb) => { this.checkStatus(cb); }); - eventHub.$on('MRWidgetRebaseSuccess', cb => { + eventHub.$on('MRWidgetRebaseSuccess', (cb) => { this.checkStatus(cb, true); }); // `params` should be an Array contains a Boolean, like `[true]` // Passing parameter as Boolean didn't work. - eventHub.$on('SetBranchRemoveFlag', params => { + eventHub.$on('SetBranchRemoveFlag', (params) => { [this.mr.isRemovingSourceBranch] = params; }); - eventHub.$on('FailedToMerge', mergeError => { + eventHub.$on('FailedToMerge', (mergeError) => { this.mr.state = 'failedToMerge'; this.mr.mergeError = mergeError; }); - eventHub.$on('UpdateWidgetData', data => { + eventHub.$on('UpdateWidgetData', (data) => { this.mr.setData(data); }); diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql new file mode 100644 index 00000000000..64cd70fcf42 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql @@ -0,0 +1,15 @@ +fragment autoMergeEnabled on MergeRequest { + autoMergeStrategy + mergeUser { + name + username + webUrl + avatarUrl + } + targetBranch + shouldRemoveSourceBranch + forceRemoveSourceBranch + userPermissions { + removeSourceBranch + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql new file mode 100644 index 00000000000..bdcb7a8206b --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql @@ -0,0 +1,10 @@ +#import "./auto_merge_enabled.fragment.graphql" + +query autoMergeEnabledQuery($projectPath: ID!, $iid: String!) { + project(fullPath: $projectPath) { + mergeRequest(iid: $iid) { + ...autoMergeEnabled + mergeTrainsCount + } + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql new file mode 100644 index 00000000000..2fe0d174b67 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql @@ -0,0 +1,7 @@ +query autoMergeFailedQuery($projectPath: ID!, $iid: String!) { + project(fullPath: $projectPath) { + mergeRequest(iid: $iid) { + mergeError + } + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql new file mode 100644 index 00000000000..9479ef3cf79 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql @@ -0,0 +1,41 @@ +fragment ReadyToMerge on Project { + onlyAllowMergeIfPipelineSucceeds + mergeRequestsFfOnlyEnabled + squashReadOnly + mergeRequest(iid: $iid) { + autoMergeEnabled + shouldRemoveSourceBranch + defaultMergeCommitMessage + defaultMergeCommitMessageWithDescription + defaultSquashCommitMessage + squash + squashOnMerge + availableAutoMergeStrategies + hasCi + mergeable + mergeWhenPipelineSucceeds + commitCount + diffHeadSha + userPermissions { + removeSourceBranch + } + targetBranch + mergeError + commitsWithoutMergeCommits { + nodes { + sha + shortId + title + message + } + } + pipelines(first: 1) { + nodes { + id + status + path + active + } + } + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql new file mode 100644 index 00000000000..78259e1f553 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql @@ -0,0 +1,7 @@ +#import "./ready_to_merge.fragment.graphql" + +query readyToMergeQuery($projectPath: ID!, $iid: String!) { + project(fullPath: $projectPath) { + ...ReadyToMerge + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/rebase.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/rebase.query.graphql new file mode 100644 index 00000000000..a8c7d2610bf --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/rebase.query.graphql @@ -0,0 +1,11 @@ +query rebaseQuery($projectPath: ID!, $iid: String!) { + project(fullPath: $projectPath) { + mergeRequest(iid: $iid) { + rebaseInProgress + targetBranch + userPermissions { + pushToSourceBranch + } + } + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index 2ad15f231bb..7dcb4881e7f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -60,15 +60,15 @@ export default class MRWidgetService { } fetchApprovals() { - return axios.get(this.apiApprovalsPath).then(res => res.data); + return axios.get(this.apiApprovalsPath).then((res) => res.data); } approveMergeRequest() { - return axios.post(this.apiApprovePath).then(res => res.data); + return axios.post(this.apiApprovePath).then((res) => res.data); } unapproveMergeRequest() { - return axios.post(this.apiUnapprovePath).then(res => res.data); + return axios.post(this.apiUnapprovePath).then((res) => res.data); } static executeInlineAction(url) { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js index 15d67ea18ea..7740147c8e4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js @@ -1,6 +1,6 @@ import { s__, n__ } from '~/locale'; -export const title = state => { +export const title = (state) => { if (state.isLoading) { return s__('BuildArtifacts|Loading artifacts'); } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/index.js b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/index.js index f8abbc99f0f..e6cb5ead089 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/index.js @@ -7,10 +7,11 @@ import state from './state'; Vue.use(Vuex); -export default () => - new Vuex.Store({ - actions, - mutations, - getters, - state: state(), - }); +export const getStoreConfig = () => ({ + actions, + mutations, + getters, + state: state(), +}); + +export default () => new Vuex.Store(getStoreConfig()); diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index f50b6caf0f5..a6bbab47a06 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -1,5 +1,6 @@ import { format } from 'timeago.js'; import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key'; +import mrEventHub from '~/merge_request/eventhub'; import { stateKey } from './state_maps'; import { formatDate } from '../../lib/utils/datetime_utility'; import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants'; @@ -154,6 +155,10 @@ export default class MergeRequestStore { this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; this.setState(); + + mrEventHub.$emit('mr.state.updated', { + state: this.mergeRequestState, + }); } setGraphqlData(project) { @@ -167,7 +172,7 @@ export default class MergeRequestStore { this.canBeMerged = mergeRequest.mergeStatus === 'can_be_merged'; this.canMerge = mergeRequest.userPermissions.canMerge; this.ciStatus = pipeline?.status.toLowerCase(); - this.commitsCount = mergeRequest.commitCount; + this.commitsCount = mergeRequest.commitCount || 10; this.branchMissing = !mergeRequest.sourceBranchExists || !mergeRequest.targetBranchExists; this.hasConflicts = mergeRequest.conflicts; this.hasMergeableDiscussionsState = mergeRequest.mergeableDiscussionsState === false; |