diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
39 files changed, 759 insertions, 835 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue index b76d5d90ead..38f40e8a3c8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue @@ -14,7 +14,8 @@ export default { props: { widget: { type: String, - required: true, + required: false, + default: '', }, tertiaryButtons: { type: Array, @@ -30,6 +31,8 @@ export default { }, computed: { dropdownLabel() { + if (!this.widget) return undefined; + return sprintf(__('%{widget} options'), { widget: this.widget }); }, }, @@ -85,6 +88,7 @@ export default { :href="btn.href" :target="btn.target" :data-clipboard-text="btn.dataClipboardText" + :data-method="btn.dataMethod" @click="onClickAction(btn)" > {{ btn.text }} @@ -99,11 +103,15 @@ export default { :title="setTooltip(btn)" :href="btn.href" :target="btn.target" - :class="{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }" + :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]" :data-clipboard-text="btn.dataClipboardText" + :data-qa-selector="btn.dataQaSelector" + :data-method="btn.dataMethod" :icon="btn.icon" :data-testid="btn.testId || 'extension-actions-button'" :variant="btn.variant || 'confirm'" + :loading="btn.loading" + :disabled="btn.loading" category="tertiary" size="small" class="gl-display-none gl-md-display-block gl-float-left" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue index 437d035fbf5..254b280bf14 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue @@ -2,9 +2,9 @@ import { GlSprintf } from '@gitlab/ui'; import { escape } from 'lodash'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { n__, s__ } from '~/locale'; +import { n__, s__, sprintf } from '~/locale'; -const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit'); +const mergeCommitCount = s__('mrWidgetCommitsAdded|%{strongStart}1%{strongEnd} merge commit'); export default { components: { @@ -49,40 +49,45 @@ export default { return escape(this.targetBranch); }, commitsCountMessage() { - return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount); + const count = this.isSquashEnabled ? 1 : this.commitsCount; + + return sprintf( + n__( + '%{strongStart}%{count}%{strongEnd} commit', + '%{strongStart}%{count}%{strongEnd} commits', + count, + ), + { count }, + ); }, message() { - if (this.glFeatures.restructuredMrWidget) { - if (this.state === 'closed') { - return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.'); - } else if (this.isMerged) { - return s__( - 'mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}.', - ); - } - - return this.isFastForwardEnabled - ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.') - : s__( - 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.', - ); + if (this.state === 'closed') { + return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.'); + } else if (this.isMerged) { + return s__( + 'mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}.', + ); } return this.isFastForwardEnabled - ? s__('mrWidgetCommitsAdded|Adds %{commitCount} to %{targetBranch}.') + ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.') : s__( - 'mrWidgetCommitsAdded|Adds %{commitCount} and %{mergeCommitCount} to %{targetBranch}%{squashedCommits}.', + 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.', ); }, - textDecorativeComponent() { - return this.glFeatures.restructuredMrWidget ? 'span' : 'strong'; - }, squashCommitMessage() { if (this.isMerged) { - return s__('mergedCommitsAdded|(commits were squashed)'); + return s__('mergedCommitsAdded| (commits were squashed)'); } - return n__('(squashes %d commit)', '(squashes %d commits)', this.commitsCount); + return sprintf( + n__( + ' (squashes %{strongStart}%{count}%{strongEnd} commit)', + ' (squashes %{strongStart}%{count}%{strongEnd} commits)', + this.commitsCount, + ), + { count: this.commitsCount }, + ); }, }, mergeCommitCount, @@ -93,25 +98,33 @@ export default { <span> <gl-sprintf :message="message"> <template #commitCount> - <component :is="textDecorativeComponent" class="commits-count-message">{{ - commitsCountMessage - }}</component> + <gl-sprintf :message="commitsCountMessage"> + <template #strong="{ content }"> + <span class="gl-font-weight-bold">{{ content }}</span> + </template> + </gl-sprintf> </template> <template #mergeCommitCount> - <component :is="textDecorativeComponent">{{ $options.mergeCommitCount }}</component> + <gl-sprintf :message="$options.mergeCommitCount"> + <template #strong="{ content }"> + <span class="gl-font-weight-bold">{{ content }}</span> + </template> + </gl-sprintf> </template> <template #targetBranch> - <span class="label-branch">{{ targetBranchEscaped }}</span> + <span class="label-branch gl-font-weight-bold">{{ targetBranchEscaped }}</span> </template> <template #squashedCommits> - <template v-if="glFeatures.restructuredMrWidget && isSquashEnabled"> - {{ squashCommitMessage }}</template - ></template - > + <template v-if="isSquashEnabled"> + <gl-sprintf :message="squashCommitMessage"> + <template #strong="{ content }"> + <span class="gl-font-weight-bold">{{ content }}</span> + </template> + </gl-sprintf> + </template> + </template> <template #mergeCommitSha> - <template v-if="glFeatures.restructuredMrWidget" - ><span class="label-branch">{{ mergeCommitSha }}</span></template - > + <span class="label-branch">{{ mergeCommitSha }}</span> </template> </gl-sprintf> </span> 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 4163d195e0f..f782c28ea19 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 @@ -4,9 +4,6 @@ import createFlash from '~/flash'; import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { s__, __ } from '~/locale'; -import sidebarEventHub from '~/sidebar/event_hub'; -import showToast from '~/vue_shared/plugins/global_toast'; -import SidebarMediator from '~/sidebar/sidebar_mediator'; import eventHub from '../../event_hub'; import approvalsMixin from '../../mixins/approvals'; import MrWidgetContainer from '../mr_widget_container.vue'; @@ -192,16 +189,8 @@ export default { .then((data) => { this.mr.setApprovals(data); - if ( - this.glFeatures.mrAttentionRequests && - SidebarMediator.singleton?.store.currentUserHasAttention - ) { - showToast(__('Approved. Your attention request was removed.')); - } - eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('ApprovalUpdated'); - sidebarEventHub.$emit('removeCurrentUserAttentionRequested'); this.$emit('updated'); }) .catch(errFn) 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 bb1837399ed..1256b3a8e52 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 @@ -80,22 +80,26 @@ export default { }, computeGraphData(metrics, deploymentTime) { this.loadingMetrics = false; - const { memory_before, memory_after, memory_values } = metrics; + const { + memory_before: memoryBefore, + memory_after: memoryAfter, + memory_values: memoryValues, + } = metrics; // Both `memory_before` and `memory_after` objects // have peculiar structure where accessing only a specific // index yeilds correct value that we can use to show memory delta. - if (memory_before.length > 0) { - this.memoryFrom = this.getMegabytes(memory_before[0].value[1]); + if (memoryBefore.length > 0) { + this.memoryFrom = this.getMegabytes(memoryBefore[0].value[1]); } - if (memory_after.length > 0) { - this.memoryTo = this.getMegabytes(memory_after[0].value[1]); + if (memoryAfter.length > 0) { + this.memoryTo = this.getMegabytes(memoryAfter[0].value[1]); } - if (memory_values.length > 0) { + if (memoryValues.length > 0) { this.hasMetrics = true; - this.memoryMetrics = memory_values[0].values; + this.memoryMetrics = memoryValues[0].values; this.deploymentTime = deploymentTime; } }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/README.md b/app/assets/javascripts/vue_merge_request_widget/components/extensions/README.md new file mode 100644 index 00000000000..45ebafec8bf --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/README.md @@ -0,0 +1 @@ +Please see [the Widget Extensions documentation](development/merge_request_concepts/widget_extensions.md) for necessary information regarding development of new MR Widgets. 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 410331004e4..414c5bf9691 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 @@ -12,8 +12,8 @@ import { sprintf, s__, __ } from '~/locale'; import Poll from '~/lib/utils/poll'; import { normalizeHeaders } from '~/lib/utils/common_utils'; import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants'; +import Actions from '../action_buttons.vue'; import StatusIcon from './status_icon.vue'; -import Actions from './actions.vue'; import ChildContent from './child_content.vue'; import { createTelemetryHub } from './telemetry'; import { generateText } from './utils'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue index 38f83a61b30..1eccc7de660 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue @@ -1,7 +1,7 @@ <script> import { GlBadge, GlLink, GlSafeHtmlDirective, GlModalDirective } from '@gitlab/ui'; +import Actions from '../action_buttons.vue'; import StatusIcon from './status_icon.vue'; -import Actions from './actions.vue'; import { generateText } from './utils'; export default { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js index b551cd2fd60..bc84459e298 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js @@ -34,6 +34,36 @@ const nonStandardEvents = { }, counter: {}, }, + metrics: { + uniqueUser: { + expand: ['i_testing_metrics_report_widget_total'], + }, + counter: {}, + }, + browserPerformance: { + uniqueUser: { + expand: ['i_testing_web_performance_widget_total'], + }, + counter: {}, + }, + licenseCompliance: { + uniqueUser: { + expand: ['i_testing_license_compliance_widget_total'], + }, + counter: {}, + }, + loadPerformance: { + uniqueUser: { + expand: ['i_testing_load_performance_widget_total'], + }, + counter: {}, + }, + statusChecks: { + uniqueUser: { + expand: ['i_testing_status_checks_widget'], + }, + counter: {}, + }, }; function combineDeepArray(path, ...objects) { 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 913aa0e1e34..94a1b805b99 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 @@ -1,7 +1,6 @@ <script> -import { GlSafeHtmlDirective as SafeHtml, GlLink, GlSprintf } from '@gitlab/ui'; +import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui'; import { s__, n__ } from '~/locale'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'MRWidgetRelatedLinks', @@ -10,9 +9,7 @@ export default { }, components: { GlLink, - GlSprintf, }, - mixins: [glFeatureFlagMixin()], props: { relatedLinks: { type: Object, @@ -67,42 +64,21 @@ export default { </script> <template> <section> - <p - v-if="relatedLinks.closing" - :class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }" - > + <p v-if="relatedLinks.closing" class="gl-display-inline gl-m-0"> {{ closesText }} <span v-safe-html="relatedLinks.closing"></span> </p> - <p - v-if="relatedLinks.mentioned" - :class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }" - > - <span v-if="relatedLinks.closing && glFeatures.restructuredMrWidget">·</span> + <p v-if="relatedLinks.mentioned" class="gl-display-inline gl-m-0"> + <span v-if="relatedLinks.closing">·</span> {{ n__('mrWidget|Mentions issue', 'mrWidget|Mentions issues', relatedLinks.mentionedCount) }} <span v-safe-html="relatedLinks.mentioned"></span> </p> - <p - v-if="shouldShowAssignToMeLink" - :class="{ 'gl-display-inline gl-m-0': glFeatures.restructuredMrWidget }" - > + <p v-if="shouldShowAssignToMeLink" class="gl-display-inline gl-m-0"> <span> <gl-link rel="nofollow" data-method="post" :href="relatedLinks.assignToMe">{{ assignIssueText }}</gl-link> </span> </p> - <div - v-if="divergedCommitsCount > 0 && !glFeatures.restructuredMrWidget" - class="diverged-commits-count" - > - <gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')"> - <template #link> - <gl-link :href="targetBranchPath">{{ - n__('%d commit behind', '%d commits behind', divergedCommitsCount) - }}</gl-link> - </template> - </gl-sprintf> - </div> </section> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 7ff1eb6e73a..5b8acb4ebf8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,25 +1,17 @@ <script> -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { GlLoadingIcon } from '@gitlab/ui'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; export default { components: { ciIcon, - GlButton, GlLoadingIcon, }, - mixins: [glFeatureFlagMixin()], props: { status: { type: String, required: true, }, - showDisabledButton: { - type: Boolean, - required: false, - default: false, - }, }, computed: { isLoading() { @@ -42,15 +34,5 @@ export default { </div> <ci-icon v-else :status="statusObj" :size="24" /> </div> - - <gl-button - v-if="!glFeatures.restructuredMrWidget && showDisabledButton" - category="primary" - variant="confirm" - data-testid="disabled-merge-button" - :disabled="true" - > - {{ s__('mrWidget|Merge') }} - </gl-button> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue new file mode 100644 index 00000000000..4a5a03fb598 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue @@ -0,0 +1,55 @@ +<script> +import StatusIcon from './mr_widget_status_icon.vue'; +import Actions from './action_buttons.vue'; + +export default { + components: { + StatusIcon, + Actions, + }, + props: { + isLoading: { + type: Boolean, + required: false, + default: false, + }, + status: { + type: String, + required: false, + default: '', + }, + actions: { + type: Array, + required: false, + default: () => [], + }, + }, +}; +</script> + +<template> + <div class="mr-widget-body media"> + <div v-if="isLoading" class="gl-w-full mr-conflict-loader"> + <slot name="loading"></slot> + </div> + <template v-else> + <slot name="icon"> + <status-icon :status="status" /> + </slot> + <div + :class="{ 'gl-display-flex': actions.length, 'gl-md-display-flex': !actions.length }" + class="media-body" + > + <slot></slot> + <div + :class="{ 'gl-flex-direction-column-reverse': !actions.length }" + class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1" + > + <slot name="actions"> + <actions v-if="actions.length" :tertiary-buttons="actions" /> + </slot> + </div> + </div> + </template> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue index 18761d04c2e..515a7cf51a1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue @@ -1,8 +1,5 @@ <script> -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; - export default { - mixins: [glFeatureFlagMixin()], props: { value: { type: String, @@ -23,10 +20,7 @@ export default { <template> <li> <div class="commit-message-editor"> - <div - :class="{ 'gl-mb-3': glFeatures.restructuredMrWidget }" - class="d-flex flex-wrap align-items-center justify-content-between" - > + <div class="d-flex flex-wrap align-items-center justify-content-between gl-mb-3"> <label class="col-form-label" :for="inputId"> <strong>{{ label }}</strong> </label> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue index 071920856a8..f74826f95d3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue @@ -1,5 +1,4 @@ <script> -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import statusIcon from '../mr_widget_status_icon.vue'; export default { @@ -7,7 +6,6 @@ export default { components: { statusIcon, }, - mixins: [glFeatureFlagMixin()], }; </script> <template> @@ -16,7 +14,7 @@ export default { <status-icon status="warning" show-disabled-button /> </div> <div class="media-body"> - <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold"> + <span class="gl-ml-0! gl-text-body! bold"> {{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }} </span> </div> 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 aabbeac564a..690acc9a6dc 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,15 +1,15 @@ <script> -import { GlSkeletonLoader, GlIcon, GlButton, GlSprintf } from '@gitlab/ui'; +import { GlSkeletonLoader, GlIcon, GlSprintf } 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 createFlash from '~/flash'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { AUTO_MERGE_STRATEGIES } from '../../constants'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import MrWidgetAuthor from '../mr_widget_author.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetAutoMergeEnabled', @@ -29,8 +29,8 @@ export default { MrWidgetAuthor, GlSkeletonLoader, GlIcon, - GlButton, GlSprintf, + StateContainer, }, mixins: [autoMergeMixin, glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], props: { @@ -78,18 +78,25 @@ export default { autoMergeStrategy() { return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).autoMergeStrategy; }, - canRemoveSourceBranch() { - const { currentUserId } = this.mr; - const mergeUserId = this.glFeatures.mergeRequestWidgetGraphql - ? getIdFromGraphQLId(this.state.mergeUser?.id) - : this.mr.mergeUserId; - const canRemoveSourceBranch = this.glFeatures.mergeRequestWidgetGraphql - ? this.state.userPermissions.removeSourceBranch - : this.mr.canRemoveSourceBranch; + actions() { + const actions = []; - return ( - !this.shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId - ); + if (this.loading) { + return actions; + } + + if (this.mr.canCancelAutomaticMerge) { + actions.push({ + text: this.cancelButtonText, + loading: this.isCancellingAutoMerge, + dataQaSelector: 'cancel_auto_merge_button', + class: 'js-cancel-auto-merge', + testId: 'cancelAutomaticMergeButton', + onClick: () => this.cancelAutomaticMerge(), + }); + } + + return actions; }, }, methods: { @@ -144,56 +151,25 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <div v-if="loading" class="gl-w-full mr-conflict-loader"> + <state-container status="scheduled" :is-loading="loading" :actions="actions"> + <template #loading> <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> + </template> + <template v-if="!loading"> + <h4 class="gl-mr-3" data-testid="statusText"> + <gl-sprintf :message="statusText" data-testid="statusText"> + <template #merge_author> + <mr-widget-author :author="mergeUser" /> + </template> + </gl-sprintf> + </h4> + </template> + <template v-if="!loading" #icon> <gl-icon name="status_scheduled" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" /> - <div class="media-body"> - <h4 class="gl-display-flex"> - <span class="gl-mr-3"> - <gl-sprintf :message="statusText" data-testid="statusText"> - <template #merge_author> - <mr-widget-author :author="mergeUser" /> - </template> - </gl-sprintf> - </span> - <gl-button - v-if="mr.canCancelAutomaticMerge" - :loading="isCancellingAutoMerge" - size="small" - class="js-cancel-auto-merge" - data-qa-selector="cancel_auto_merge_button" - data-testid="cancelAutomaticMergeButton" - @click="cancelAutomaticMerge" - > - {{ cancelButtonText }} - </gl-button> - </h4> - <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list"> - <p v-if="shouldRemoveSourceBranch"> - {{ s__('mrWidget|Deletes the source branch') }} - </p> - <p v-else class="gl-display-flex"> - <span class="gl-mr-3">{{ s__('mrWidget|Does not delete the source branch') }}</span> - <gl-button - v-if="canRemoveSourceBranch" - :loading="isRemovingSourceBranch" - size="small" - class="js-remove-source-branch" - data-testid="removeSourceBranchButton" - @click="removeSourceBranch" - > - {{ s__('mrWidget|Delete source branch') }} - </gl-button> - </p> - </section> - </div> </template> - </div> + </state-container> </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 1a764d3d091..b0cda85f361 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,17 +1,15 @@ <script> -import { GlLoadingIcon, GlButton } from '@gitlab/ui'; +import { s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import autoMergeFailedQuery from '../../queries/states/auto_merge_failed.query.graphql'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetAutoMergeFailed', components: { - statusIcon, - GlLoadingIcon, - GlButton, + StateContainer, }, mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { @@ -38,6 +36,17 @@ export default { isRefreshing: false, }; }, + computed: { + actions() { + return [ + { + text: s__('mrWidget|Refresh'), + loading: this.isRefreshing, + onClick: () => this.refreshWidget(), + }, + ]; + }, + }, methods: { refreshWidget() { this.isRefreshing = true; @@ -49,23 +58,10 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <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="mergeError">{{ mergeError }}</template> - {{ s__('mrWidget|This merge request failed to be merged automatically') }} - </span> - <gl-button - :disabled="isRefreshing" - category="secondary" - variant="default" - size="small" - @click="refreshWidget" - > - <gl-loading-icon v-if="isRefreshing" size="sm" :inline="true" /> - {{ s__('mrWidget|Refresh') }} - </gl-button> - </div> - </div> + <state-container status="warning" :actions="actions"> + <span class="bold gl-ml-0!"> + <template v-if="mergeError">{{ mergeError }}</template> + {{ s__('mrWidget|This merge request failed to be merged automatically') }} + </span> + </state-container> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue index fd42fa0421f..e2d87d8d536 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue @@ -1,5 +1,4 @@ <script> -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import statusIcon from '../mr_widget_status_icon.vue'; export default { @@ -7,14 +6,13 @@ export default { components: { statusIcon, }, - mixins: [glFeatureFlagMixin()], }; </script> <template> <div class="mr-widget-body media"> <status-icon :show-disabled-button="true" status="loading" /> <div class="media-body space-children"> - <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold"> + <span class="gl-ml-0! gl-text-body! bold"> {{ s__('mrWidget|Checking if merge request can be merged…') }} </span> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue index d50e52f5ac1..61f7d26f51e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue @@ -1,5 +1,4 @@ <script> -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MrWidgetAuthorTime from '../mr_widget_author_time.vue'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -9,7 +8,6 @@ export default { MrWidgetAuthorTime, statusIcon, }, - mixins: [glFeatureFlagMixin()], props: { /* TODO: This is providing all store and service down when it only needs metrics and targetBranch */ @@ -30,13 +28,6 @@ export default { :date-title="mr.metrics.closedAt" :date-readable="mr.metrics.readableClosedAt" /> - - <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list"> - <p> - {{ s__('mrWidget|The changes were not merged into') }} - <a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a> - </p> - </section> </div> </div> </template> 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 def30dacf8a..8abd915b93e 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 @@ -4,14 +4,14 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import userPermissionsQuery from '../../queries/permissions.query.graphql'; import conflictsStateQuery from '../../queries/states/conflicts.query.graphql'; -import StatusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetConflicts', components: { GlSkeletonLoader, - StatusIcon, GlButton, + StateContainer, }, mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { @@ -86,29 +86,23 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="warning" /> - - <div v-if="isLoading" class="gl-ml-4 gl-w-full mr-conflict-loader"> + <state-container status="warning" :is-loading="isLoading"> + <template #loading> <gl-skeleton-loader :width="334" :height="30"> <rect x="0" y="7" width="150" height="16" rx="4" /> <rect x="158" y="7" width="84" height="16" rx="4" /> <rect x="250" y="7" width="84" height="16" rx="4" /> </gl-skeleton-loader> - </div> - <div v-else class="media-body space-children gl-display-flex gl-align-items-center"> - <span - v-if="shouldBeRebased" - :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" - class="bold" - > + </template> + <template v-if="!isLoading"> + <span v-if="shouldBeRebased" class="bold gl-ml-0! gl-text-body!"> {{ s__(`mrWidget|Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.`) }} </span> <template v-else> - <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold"> + <span class="bold gl-ml-0! gl-text-body! gl-flex-grow-1 gl-w-full gl-md-w-auto gl-mr-2"> {{ s__('mrWidget|Merge blocked: merge conflicts must be resolved.') }} <span v-if="!canMerge"> {{ @@ -118,23 +112,30 @@ export default { }} </span> </span> - <gl-button - v-if="showResolveButton" - :href="mr.conflictResolutionPath" - :size="glFeatures.restructuredMrWidget ? 'small' : 'medium'" - data-testid="resolve-conflicts-button" - > - {{ s__('mrWidget|Resolve conflicts') }} - </gl-button> - <gl-button - v-if="canMerge" - :size="glFeatures.restructuredMrWidget ? 'small' : 'medium'" - data-testid="merge-locally-button" - class="js-check-out-modal-trigger" - > - {{ s__('mrWidget|Resolve locally') }} - </gl-button> </template> - </div> - </div> + </template> + <template v-if="!isLoading && !shouldBeRebased" #actions> + <gl-button + v-if="canMerge" + size="small" + variant="confirm" + category="secondary" + data-testid="merge-locally-button" + class="js-check-out-modal-trigger gl-align-self-start" + :class="{ 'gl-mr-2': showResolveButton }" + > + {{ s__('mrWidget|Resolve locally') }} + </gl-button> + <gl-button + v-if="showResolveButton" + :href="mr.conflictResolutionPath" + size="small" + variant="confirm" + class="gl-mb-2 gl-md-mb-0 gl-align-self-start" + data-testid="resolve-conflicts-button" + > + {{ s__('mrWidget|Resolve conflicts') }} + </gl-button> + </template> + </state-container> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue index 42e9261b82c..18103ac4a0e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue @@ -30,7 +30,7 @@ export default { computed: { mergeError() { - const mergeError = this.mr.mergeError ? stripHtml(this.mr.mergeError, ' ').trim() : ''; + const mergeError = this.prepareMergeError(this.mr.mergeError); return sprintf( s__('mrWidget|%{mergeError}.'), @@ -76,6 +76,13 @@ export default { this.refresh(); } }, + prepareMergeError(mergeError) { + return mergeError + ? stripHtml(mergeError, ' ') + .replace(/(\.$|\s+)/g, ' ') + .trim() + : ''; + }, }, }; </script> @@ -89,7 +96,9 @@ export default { <status-icon :show-disabled-button="true" status="warning" /> <div class="media-body space-children"> <span class="bold"> - <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }} </span> + <span v-if="mr.mergeError" class="has-error-message" data-testid="merge-error"> + {{ mergeError }} + </span> <span v-else> {{ s__('mrWidget|Merge failed.') }} </span> <span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span> </span> 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 bf036f562ed..4416123cd51 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 @@ -1,15 +1,13 @@ <script> -/* eslint-disable @gitlab/vue-require-i18n-strings */ -import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; import api from '~/api'; import createFlash from '~/flash'; import { s__, __ } from '~/locale'; import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants'; import modalEventHub from '~/projects/commit/event_hub'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import eventHub from '../../event_hub'; import MrWidgetAuthorTime from '../mr_widget_author_time.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetMerged', @@ -19,11 +17,8 @@ export default { components: { MrWidgetAuthorTime, GlIcon, - ClipboardButton, - GlLoadingIcon, - GlButton, + StateContainer, }, - mixins: [glFeatureFlagMixin()], props: { mr: { type: Object, @@ -78,6 +73,53 @@ export default { cherryPickLabel() { return s__('mrWidget|Cherry-pick'); }, + actions() { + const actions = []; + + if (this.mr.canRevertInCurrentMR) { + actions.push({ + text: this.revertLabel, + tooltipText: this.revertTitle, + dataQaSelector: 'revert_button', + onClick: () => this.openRevertModal(), + }); + } else if (this.mr.revertInForkPath) { + actions.push({ + text: this.revertLabel, + tooltipText: this.revertTitle, + href: this.mr.revertInForkPath, + dataQaSelector: 'revert_button', + dataMethod: 'post', + }); + } + + if (this.mr.canCherryPickInCurrentMR) { + actions.push({ + text: this.cherryPickLabel, + tooltipText: this.cherryPickTitle, + dataQaSelector: 'cherry_pick_button', + onClick: () => this.openCherryPickModal(), + }); + } else if (this.mr.cherryPickInForkPath) { + actions.push({ + text: this.cherryPickLabel, + tooltipText: this.cherryPickTitle, + href: this.mr.cherryPickInForkPath, + dataQaSelector: 'cherry_pick_button', + dataMethod: 'post', + }); + } + + if (this.shouldShowRemoveSourceBranch) { + actions.push({ + text: s__('mrWidget|Delete source branch'), + class: 'js-remove-branch-button', + onClick: () => this.removeSourceBranch(), + }); + } + + return actions; + }, }, mounted() { document.dispatchEvent(new CustomEvent('merged:UpdateActions')); @@ -121,103 +163,15 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" /> - <div class="media-body"> - <div class="space-children"> - <mr-widget-author-time - :action-text="s__('mrWidget|Merged by')" - :author="mr.metrics.mergedBy" - :date-title="mr.metrics.mergedAt" - :date-readable="mr.metrics.readableMergedAt" - /> - <gl-button - v-if="mr.canRevertInCurrentMR" - v-gl-tooltip.hover - :title="revertTitle" - size="small" - category="secondary" - data-qa-selector="revert_button" - @click="openRevertModal" - > - {{ revertLabel }} - </gl-button> - <gl-button - v-else-if="mr.revertInForkPath" - v-gl-tooltip.hover - :href="mr.revertInForkPath" - :title="revertTitle" - size="small" - category="secondary" - data-method="post" - > - {{ revertLabel }} - </gl-button> - <gl-button - v-if="mr.canCherryPickInCurrentMR" - v-gl-tooltip.hover - :title="cherryPickTitle" - size="small" - data-qa-selector="cherry_pick_button" - @click="openCherryPickModal" - > - {{ cherryPickLabel }} - </gl-button> - <gl-button - v-else-if="mr.cherryPickInForkPath" - v-gl-tooltip.hover - :href="mr.cherryPickInForkPath" - :title="cherryPickTitle" - size="small" - data-method="post" - > - {{ cherryPickLabel }} - </gl-button> - <gl-button - v-if="shouldShowRemoveSourceBranch" - :disabled="isMakingRequest" - size="small" - class="js-remove-branch-button" - @click="removeSourceBranch" - > - {{ s__('mrWidget|Delete source branch') }} - </gl-button> - </div> - <section - v-if="!glFeatures.restructuredMrWidget" - class="mr-info-list" - data-qa-selector="merged_status_content" - > - <p> - {{ s__('mrWidget|The changes were merged into') }} - <span class="label-branch"> - <a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a> - </span> - <template v-if="mr.mergeCommitSha"> - with - <a - :href="mr.mergeCommitPath" - class="commit-sha js-mr-merged-commit-sha" - v-text="mr.shortMergeCommitSha" - > - </a> - <clipboard-button - :title="__('Copy commit SHA')" - :text="mr.mergeCommitSha" - css-class="js-mr-merged-copy-sha" - category="tertiary" - size="small" - /> - </template> - </p> - <p v-if="mr.sourceBranchRemoved"> - {{ s__('mrWidget|The source branch has been deleted') }} - </p> - <p v-if="shouldShowSourceBranchRemoving"> - <gl-loading-icon size="sm" :inline="true" /> - <span> {{ s__('mrWidget|The source branch is being deleted') }} </span> - </p> - </section> - </div> - </div> + <state-container :actions="actions"> + <template #icon> + <gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" /> + </template> + <mr-widget-author-time + :action-text="s__('mrWidget|Merged by')" + :author="mr.metrics.mergedBy" + :date-title="mr.metrics.mergedAt" + :date-readable="mr.metrics.readableMergedAt" + /> + </state-container> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue index b86ab69af3f..c7574a41bb8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue @@ -1,6 +1,5 @@ <script> import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import simplePoll from '~/lib/utils/simple_poll'; import MergeRequest from '~/merge_request'; import eventHub from '../../event_hub'; @@ -15,7 +14,6 @@ export default { components: { statusIcon, }, - mixins: [glFeatureFlagMixin()], props: { mr: { type: Object, @@ -90,14 +88,6 @@ export default { {{ mergeStatus.message }} <gl-emoji :data-name="mergeStatus.emoji" /> </h4> - <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list"> - <p> - {{ s__('mrWidget|Merges changes into') }} - <span class="label-branch"> - <a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a> - </span> - </p> - </section> </div> </div> </template> 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 cadbd9c28a9..659d12d1160 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 @@ -74,13 +74,7 @@ export default { <status-icon :show-disabled-button="true" status="warning" /> <div class="media-body space-children"> - <span - :class="{ - 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget, - }" - class="bold js-branch-text" - data-testid="widget-content" - > + <span class="gl-ml-0! gl-text-body! bold js-branch-text" data-testid="widget-content"> <gl-sprintf :message="warning"> <template #code="{ content }"> <code>{{ content }}</code> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue index 34c5a2ff2c8..e99ee59b877 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue @@ -14,7 +14,7 @@ export default { <div class="mr-widget-body media"> <status-icon :show-disabled-button="true" status="warning" /> <div class="media-body space-children"> - <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold"> + <span class="gl-ml-0! gl-text-body! bold"> {{ s__( `mrWidget|Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.`, 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 59767eb2e6e..6c5fc916799 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 @@ -8,7 +8,7 @@ import simplePoll from '~/lib/utils/simple_poll'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import rebaseQuery from '../../queries/states/rebase.query.graphql'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetRebase', @@ -25,9 +25,9 @@ export default { }, }, components: { - statusIcon, GlSkeletonLoader, GlButton, + StateContainer, }, mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], props: { @@ -51,9 +51,6 @@ export default { isLoading() { return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading; }, - showRebaseWithoutCi() { - return this.glFeatures?.rebaseWithoutCiUi; - }, rebaseInProgress() { if (this.glFeatures.mergeRequestWidgetGraphql) { return this.state.rebaseInProgress; @@ -76,6 +73,10 @@ export default { return this.mr.targetBranch; }, status() { + if (this.isLoading) { + return undefined; + } + if (this.rebaseInProgress || this.isMakingRequest) { return 'loading'; } @@ -148,92 +149,70 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <div v-if="isLoading" class="gl-w-full mr-conflict-loader"> + <state-container :status="status" :is-loading="isLoading"> + <template #loading> <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> + <template v-if="!isLoading"> + <span + v-if="rebaseInProgress || isMakingRequest" + class="gl-ml-0! gl-text-body! gl-font-weight-bold" + data-testid="rebase-message" + >{{ __('Rebase in progress') }}</span + > + <span + v-if="!rebaseInProgress && !canPushToSourceBranch" + class="gl-text-body! gl-font-weight-bold gl-ml-0!" + data-testid="rebase-message" + >{{ fastForwardMergeText }}</span + > + <div + v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest" + class="accept-merge-holder clearfix js-toggle-container media gl-md-display-flex gl-flex-wrap gl-flex-grow-1" + > <span - v-if="rebaseInProgress || isMakingRequest" - :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" - class="gl-font-weight-bold" + v-if="!rebasingError" + class="gl-font-weight-bold gl-w-100 gl-md-w-auto gl-flex-grow-1 gl-ml-0! gl-text-body! gl-md-mr-3" data-testid="rebase-message" - >{{ __('Rebase in progress') }}</span + data-qa-selector="no_fast_forward_message_content" + >{{ + __('Merge blocked: the source branch must be rebased onto the target branch.') + }}</span > <span - v-if="!rebaseInProgress && !canPushToSourceBranch" - :class="{ 'gl-text-body!': glFeatures.restructuredMrWidget }" - class="gl-font-weight-bold gl-ml-0!" + v-else + class="gl-font-weight-bold danger gl-w-100 gl-md-w-auto gl-flex-grow-1 gl-md-mr-3" data-testid="rebase-message" - >{{ fastForwardMergeText }}</span - > - <div - v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest" - class="accept-merge-holder clearfix js-toggle-container accept-action media space-children gl-align-items-center" + >{{ rebasingError }}</span > - <gl-button - v-if="!glFeatures.restructuredMrWidget" - :loading="isMakingRequest" - variant="confirm" - data-qa-selector="mr_rebase_button" - data-testid="standard-rebase-button" - @click="rebase" - > - {{ __('Rebase') }} - </gl-button> - <gl-button - v-if="!glFeatures.restructuredMrWidget && showRebaseWithoutCi" - :loading="isMakingRequest" - variant="confirm" - category="secondary" - data-testid="rebase-without-ci-button" - @click="rebaseWithoutCi" - > - {{ __('Rebase without pipeline') }} - </gl-button> - <span - v-if="!rebasingError" - :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" - class="gl-font-weight-bold" - data-testid="rebase-message" - data-qa-selector="no_fast_forward_message_content" - >{{ - __('Merge blocked: the source branch must be rebased onto the target branch.') - }}</span - > - <span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{ - rebasingError - }}</span> - <gl-button - v-if="glFeatures.restructuredMrWidget" - :loading="isMakingRequest" - variant="confirm" - size="small" - data-qa-selector="mr_rebase_button" - class="gl-ml-3!" - @click="rebase" - > - {{ __('Rebase') }} - </gl-button> - <gl-button - v-if="glFeatures.restructuredMrWidget && showRebaseWithoutCi" - :loading="isMakingRequest" - variant="confirm" - size="small" - category="secondary" - data-testid="rebase-without-ci-button" - @click="rebaseWithoutCi" - > - {{ __('Rebase without pipeline') }} - </gl-button> - </div> </div> </template> - </div> + <template v-if="!isLoading" #actions> + <gl-button + :loading="isMakingRequest" + variant="confirm" + size="small" + category="secondary" + data-testid="rebase-without-ci-button" + class="gl-align-self-start gl-mr-2" + @click="rebaseWithoutCi" + > + {{ __('Rebase without pipeline') }} + </gl-button> + <gl-button + :loading="isMakingRequest" + variant="confirm" + size="small" + data-qa-selector="mr_rebase_button" + data-testid="standard-rebase-button" + class="gl-mb-2 gl-md-mb-0 gl-align-self-start" + @click="rebase" + > + {{ __('Rebase') }} + </gl-button> + </template> + </state-container> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue index d204befef58..d507e5f232b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue @@ -1,7 +1,6 @@ <script> import { GlLink, GlSprintf } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { s__ } from '~/locale'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -12,7 +11,6 @@ export default { GlSprintf, statusIcon, }, - mixins: [glFeatureFlagMixin()], computed: { troubleshootingDocsPath() { return helpPagePath('ci/troubleshooting', { anchor: 'merge-request-status-messages' }); @@ -30,7 +28,7 @@ export default { <div class="mr-widget-body media"> <status-icon :show-disabled-button="true" status="warning" /> <div class="media-body space-children"> - <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold"> + <span class="gl-ml-0! gl-text-body! bold"> <gl-sprintf :message="$options.i18n.failedMessage"> <template #link="{ content }"> <gl-link :href="troubleshootingDocsPath" target="_blank"> 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 cf482410bef..d2c85b14999 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 @@ -31,12 +31,10 @@ import { import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import MergeRequestStore from '../../stores/mr_widget_store'; -import statusIcon from '../mr_widget_status_icon.vue'; import AddedCommitMessage from '../added_commit_message.vue'; import RelatedLinks from '../mr_widget_related_links.vue'; import CommitEdit from './commit_edit.vue'; import CommitMessageDropdown from './commit_message_dropdown.vue'; -import CommitsHeader from './commits_header.vue'; import SquashBeforeMerge from './squash_before_merge.vue'; import MergeFailedPipelineConfirmationDialog from './merge_failed_pipeline_confirmation_dialog.vue'; @@ -96,9 +94,7 @@ export default { }, }, components: { - statusIcon, SquashBeforeMerge, - CommitsHeader, CommitEdit, CommitMessageDropdown, GlIcon, @@ -320,34 +316,27 @@ export default { showDangerMessageForMergeTrain() { return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed; }, - restructuredWidgetShowMergeButtons() { - if (this.glFeatures.restructuredMrWidget) { - return ( - (this.isMergeAllowed || this.isAutoMergeAvailable) && - this.state.userPermissions.canMerge && - !this.mr.mergeOngoing && - !this.mr.autoMergeEnabled - ); - } - - return true; + shouldShowMergeControls() { + return ( + (this.isMergeAllowed || this.isAutoMergeAvailable) && + (this.stateData.userPermissions?.canMerge || this.mr.canMerge) && + !this.mr.mergeOngoing && + !this.mr.autoMergeEnabled + ); }, sourceBranchDeletedText() { - if (this.glFeatures.restructuredMrWidget) { - if (this.removeSourceBranch) { - return this.mr.state === 'merged' - ? __('Deleted the source branch.') - : __('Source branch will be deleted.'); - } - + if (this.removeSourceBranch) { return this.mr.state === 'merged' - ? __('Did not delete the source branch.') - : __('Source branch will not be deleted.'); + ? __('Deleted the source branch.') + : __('Source branch will be deleted.'); } - return this.removeSourceBranch - ? __('Deletes the source branch.') - : __('Does not delete the source branch.'); + return this.mr.state === 'merged' + ? __('Did not delete the source branch.') + : __('Source branch will not be deleted.'); + }, + showMergeDetailsHeader() { + return ['readyToMerge'].indexOf(this.mr.state) >= 0; }, }, mounted() { @@ -525,10 +514,7 @@ export default { <template> <div data-testid="ready_to_merge_state" - :class="{ - 'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base': - glFeatures.restructuredMrWidget, - }" + class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base" > <div v-if="loading" class="mr-widget-body"> <div class="gl-w-full mr-ready-to-merge-loader"> @@ -541,16 +527,10 @@ export default { </div> </div> <template v-else> - <div - class="mr-widget-body media" - :class="{ - 'mr-widget-body-line-height-1': glFeatures.restructuredMrWidget, - }" - > - <status-icon v-if="!glFeatures.restructuredMrWidget" :status="iconClass" /> + <div class="mr-widget-body mr-widget-body-ready-merge media mr-widget-body-line-height-1"> <div class="media-body"> <div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap"> - <gl-button-group v-if="restructuredWidgetShowMergeButtons" class="gl-align-self-start"> + <gl-button-group v-if="shouldShowMergeControls" class="gl-align-self-start"> <gl-button size="medium" category="primary" @@ -603,19 +583,14 @@ export default { <merge-train-helper-icon v-if="shouldRenderMergeTrainHelperIcon" class="gl-mx-3" /> <div v-if="shouldShowMergeControls" - :class="{ 'gl-w-full gl-order-n1 gl-mb-5': glFeatures.restructuredMrWidget }" - class="gl-display-flex gl-align-items-center gl-flex-wrap" + class="gl-display-flex gl-align-items-center gl-flex-wrap gl-w-full gl-order-n1 gl-mb-5" > <gl-form-checkbox v-if="canRemoveSourceBranch" id="remove-source-branch-input" v-model="removeSourceBranch" :disabled="isRemoveSourceBranchButtonDisabled" - :class="{ - 'gl-mx-3': !glFeatures.restructuredMrWidget, - 'gl-mr-5': glFeatures.restructuredMrWidget, - }" - class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center" + class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center gl-mr-5" > {{ __('Delete source branch') }} </gl-form-checkbox> @@ -626,16 +601,11 @@ export default { v-model="squashBeforeMerge" :help-path="mr.squashBeforeMergeHelpPath" :is-disabled="isSquashReadOnly" - :class="{ - 'gl-mx-3': !glFeatures.restructuredMrWidget, - 'gl-mr-5': glFeatures.restructuredMrWidget, - }" + class="gl-mr-5" /> <gl-form-checkbox - v-if=" - glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit) - " + v-if="shouldShowSquashEdit || shouldShowMergeEdit" v-model="editCommitMessage" data-testid="widget_edit_commit_message" class="gl-display-flex gl-align-items-center" @@ -644,198 +614,113 @@ export default { </gl-form-checkbox> </div> <div - v-else-if="!glFeatures.restructuredMrWidget" - class="bold js-resolve-mr-widget-items-message gl-ml-3" + v-if="editCommitMessage" + class="gl-w-full gl-order-n1" + data-testid="edit_commit_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" + <ul class="border-top commits-list flex-list gl-list-style-none gl-p-0 gl-pt-4"> + <commit-edit + v-if="shouldShowSquashEdit" + :value="squashCommitMessage" + :label="__('Squash commit message')" + input-id="squash-message-edit" + class="gl-m-0! gl-p-0!" + @input="setSquashCommitMessage" > - <gl-icon name="question" /> - </gl-link> - </div> - <gl-sprintf v-else :message="mergeDisabledText" /> + <template #header> + <commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" /> + </template> + </commit-edit> + <commit-edit + v-if="shouldShowMergeEdit" + :value="commitMessage" + :label="__('Merge commit message')" + input-id="merge-message-edit" + class="gl-m-0! gl-p-0!" + @input="setCommitMessage" + /> + <li class="gl-m-0! gl-p-0!"> + <p class="form-text text-muted"> + <gl-sprintf :message="commitTemplateHintText"> + <template #link="{ content }"> + <gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> + </li> + </ul> </div> - <template v-if="glFeatures.restructuredMrWidget"> - <div - v-if="editCommitMessage" - class="gl-w-full gl-order-n1" - data-testid="edit_commit_message" - > - <ul - :class="{ - 'content-list': !glFeatures.restructuredMrWidget, - 'gl-list-style-none gl-p-0 gl-pt-4': glFeatures.restructuredMrWidget, - }" - class="border-top commits-list flex-list" - > - <commit-edit - v-if="shouldShowSquashEdit" - :value="squashCommitMessage" - :label="__('Squash commit message')" - input-id="squash-message-edit" - class="gl-m-0! gl-p-0!" - @input="setSquashCommitMessage" + <div + v-if="!shouldShowMergeControls" + class="gl-w-full gl-order-n1 mr-widget-merge-details" + data-qa-selector="merged_status_content" + > + <p v-if="showMergeDetailsHeader" class="gl-mb-3 gl-text-gray-900"> + {{ __('Merge details') }} + </p> + <ul class="gl-pl-4 gl-mb-0 gl-ml-3 gl-text-gray-600"> + <li v-if="mr.divergedCommitsCount > 0" class="gl-line-height-normal"> + <gl-sprintf + :message="s__('mrWidget|The source branch is %{link} the target branch')" > - <template #header> - <commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" /> + <template #link> + <gl-link :href="mr.targetBranchPath">{{ + n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount) + }}</gl-link> </template> - </commit-edit> - <commit-edit - v-if="shouldShowMergeEdit" - :value="commitMessage" - :label="__('Merge commit message')" - input-id="merge-message-edit" - class="gl-m-0! gl-p-0!" - @input="setCommitMessage" + </gl-sprintf> + </li> + <li class="gl-line-height-normal"> + <added-commit-message + :state="mr.state" + :merge-commit-sha="mr.shortMergeCommitSha" + :is-squash-enabled="squashBeforeMerge" + :is-fast-forward-enabled="!shouldShowMergeEdit" + :commits-count="commitsCount" + :target-branch="stateData.targetBranch" /> - <li class="gl-m-0! gl-p-0!"> - <p class="form-text text-muted"> - <gl-sprintf :message="commitTemplateHintText"> - <template #link="{ content }"> - <gl-link - :href="commitTemplateHelpPage" - class="inline-link" - target="_blank" - > - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </p> - </li> - </ul> - </div> - <div - v-if="!restructuredWidgetShowMergeButtons" - class="gl-w-full gl-order-n1 gl-text-gray-500" - data-qa-selector="merged_status_content" - > - <strong v-if="mr.state !== 'closed'"> - {{ __('Merge details') }} - </strong> - <ul class="gl-pl-4 gl-m-0"> - <li v-if="mr.divergedCommitsCount > 0" class="gl-line-height-normal"> - <gl-sprintf - :message="s__('mrWidget|The source branch is %{link} the target branch')" - > - <template #link> - <gl-link :href="mr.targetBranchPath">{{ - n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount) - }}</gl-link> - </template> - </gl-sprintf> - </li> - <li class="gl-line-height-normal"> - <added-commit-message - :state="mr.state" - :merge-commit-sha="mr.shortMergeCommitSha" - :is-squash-enabled="squashBeforeMerge" - :is-fast-forward-enabled="!shouldShowMergeEdit" - :commits-count="commitsCount" - :target-branch="stateData.targetBranch" - /> - </li> - <li v-if="mr.state !== 'closed'" class="gl-line-height-normal"> - {{ sourceBranchDeletedText }} - </li> - <li v-if="mr.relatedLinks" class="gl-line-height-normal"> - <related-links - :state="mr.state" - :related-links="mr.relatedLinks" - :show-assign-to-me="false" - class="mr-ready-merge-related-links gl-display-inline" - /> - </li> - </ul> - </div> - <div - v-else - :class="{ 'gl-mb-5': restructuredWidgetShowMergeButtons }" - class="gl-w-full gl-order-n1 gl-text-gray-500" - > - <added-commit-message - :is-squash-enabled="squashBeforeMerge" - :is-fast-forward-enabled="!shouldShowMergeEdit" - :commits-count="commitsCount" - :target-branch="stateData.targetBranch" - /> - <template v-if="mr.relatedLinks"> - · + </li> + <li v-if="mr.state !== 'closed'" class="gl-line-height-normal"> + {{ sourceBranchDeletedText }} + </li> + <li v-if="mr.relatedLinks" class="gl-line-height-normal"> <related-links :state="mr.state" :related-links="mr.relatedLinks" :show-assign-to-me="false" - :diverged-commits-count="mr.divergedCommitsCount" - :target-branch-path="mr.targetBranchPath" class="mr-ready-merge-related-links gl-display-inline" /> - </template> - </div> - </template> - </div> - <div - v-if="showDangerMessageForMergeTrain && !glFeatures.restructuredMrWidget" - class="gl-mt-5 gl-text-gray-500" - data-testid="failed-pipeline-merge-train-text" - > - {{ __('The latest pipeline for this merge request did not complete successfully.') }} + </li> + </ul> + </div> + <div + v-else + :class="{ 'gl-mb-5': shouldShowMergeControls }" + class="gl-w-full gl-order-n1 gl-text-gray-500" + > + <added-commit-message + :is-squash-enabled="squashBeforeMerge" + :is-fast-forward-enabled="!shouldShowMergeEdit" + :commits-count="commitsCount" + :target-branch="stateData.targetBranch" + /> + <template v-if="mr.relatedLinks"> + · + <related-links + :state="mr.state" + :related-links="mr.relatedLinks" + :show-assign-to-me="false" + :diverged-commits-count="mr.divergedCommitsCount" + :target-branch-path="mr.targetBranchPath" + class="mr-ready-merge-related-links gl-display-inline" + /> + </template> + </div> </div> </div> </div> - <template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget"> - <div v-if="!shouldShowMergeEdit" class="mr-fast-forward-message"> - {{ __('Fast-forward merge without a merge commit') }} - </div> - <commits-header - v-if="!glFeatures.restructuredMrWidget && (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" - :value="squashCommitMessage" - :label="__('Squash commit message')" - input-id="squash-message-edit" - squash - @input="setSquashCommitMessage" - > - <template #header> - <commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" /> - </template> - </commit-edit> - <commit-edit - v-if="shouldShowMergeEdit" - :value="commitMessage" - :label="__('Merge commit message')" - input-id="merge-message-edit" - @input="setCommitMessage" - /> - <li> - <p class="form-text text-muted"> - <gl-sprintf :message="commitTemplateHintText"> - <template #link="{ content }"> - <gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </p> - </li> - </ul> - </commits-header> - </template> </template> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue index b1fbe150fcf..d149f5208fc 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue @@ -1,19 +1,17 @@ <script> import { GlButton } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { I18N_SHA_MISMATCH } from '../../i18n'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'ShaMismatch', components: { - statusIcon, GlButton, + StateContainer, }, i18n: { I18N_SHA_MISMATCH, }, - mixins: [glFeatureFlagMixin()], props: { mr: { type: Object, @@ -24,25 +22,24 @@ export default { </script> <template> - <div class="mr-widget-body media"> - <status-icon :show-disabled-button="false" status="warning" /> - <div class="media-body"> - <span - :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" - class="gl-font-weight-bold" - data-qa-selector="head_mismatch_content" - > - {{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }} - </span> + <state-container status="warning"> + <span + class="gl-font-weight-bold gl-md-mr-3 gl-flex-grow-1 gl-ml-0! gl-text-body!" + data-qa-selector="head_mismatch_content" + > + {{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }} + </span> + <template #actions> <gl-button - class="gl-ml-3" data-testid="action-button" size="small" category="primary" variant="confirm" + class="gl-align-self-start" :href="mr.mergeRequestDiffsPath" - >{{ $options.i18n.I18N_SHA_MISMATCH.actionButtonLabel }}</gl-button > - </div> - </div> + {{ $options.i18n.I18N_SHA_MISMATCH.actionButtonLabel }} + </gl-button> + </template> + </state-container> </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 c6227c4394d..1413a46b4b9 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 @@ -1,6 +1,5 @@ <script> import { GlIcon, GlTooltipDirective, GlFormCheckbox, GlLink } from '@gitlab/ui'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { SQUASH_BEFORE_MERGE } from '../../i18n'; export default { @@ -12,7 +11,6 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - mixins: [glFeatureFlagsMixin()], i18n: { ...SQUASH_BEFORE_MERGE, }, @@ -36,9 +34,6 @@ export default { tooltipTitle() { return this.isDisabled ? this.$options.i18n.tooltipTitle : null; }, - helpIconName() { - return this.glFeatures.restructuredMrWidget ? 'question-o' : 'question'; - }, }, }; </script> @@ -62,10 +57,10 @@ export default { v-gl-tooltip :href="helpPath" :title="$options.i18n.helpLabel" - :class="{ 'gl-text-blue-600': glFeatures.restructuredMrWidget }" + class="gl-text-blue-600" target="_blank" > - <gl-icon :name="helpIconName" /> + <gl-icon name="question-o" /> <span class="sr-only"> {{ $options.i18n.helpLabel }} </span> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue index 25ba4bf12af..035d62eaa59 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue @@ -1,16 +1,14 @@ <script> import { GlButton } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import notesEventHub from '~/notes/event_hub'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'UnresolvedDiscussions', components: { - statusIcon, GlButton, + StateContainer, }, - mixins: [glFeatureFlagMixin()], props: { mr: { type: Object, @@ -26,38 +24,33 @@ export default { </script> <template> - <div class="mr-widget-body media gl-flex-wrap"> - <status-icon show-disabled-button status="warning" /> - <div class="media-body"> - <span - :class="{ - 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget, - 'gl-display-block': !glFeatures.restructuredMrWidget, - }" - class="gl-ml-3 gl-font-weight-bold gl-w-100" - > - {{ s__('mrWidget|Merge blocked: all threads must be resolved.') }} - </span> + <state-container status="warning"> + <span + class="gl-ml-3 gl-font-weight-bold gl-w-100 gl-flex-grow-1 gl-md-mr-3 gl-ml-0! gl-text-body!" + > + {{ s__('mrWidget|Merge blocked: all threads must be resolved.') }} + </span> + <template #actions> <gl-button - data-testid="jump-to-first" - class="gl-ml-3" + v-if="mr.createIssueToResolveDiscussionsPath" + :href="mr.createIssueToResolveDiscussionsPath" + class="js-create-issue gl-align-self-start gl-vertical-align-top gl-mr-2" size="small" - :icon="glFeatures.restructuredMrWidget ? undefined : 'comment-next'" - :variant="glFeatures.restructuredMrWidget ? 'confirm' : 'default'" - :category="glFeatures.restructuredMrWidget ? 'secondary' : 'primary'" - @click="jumpToFirstUnresolvedDiscussion" + variant="confirm" + category="secondary" > - {{ s__('mrWidget|Jump to first unresolved thread') }} + {{ s__('mrWidget|Create issue to resolve all threads') }} </gl-button> <gl-button - v-if="mr.createIssueToResolveDiscussionsPath" - :href="mr.createIssueToResolveDiscussionsPath" - class="js-create-issue gl-ml-3" + data-testid="jump-to-first" + class="gl-mb-2 gl-md-mb-0 gl-align-self-start gl-vertical-align-top" size="small" - :icon="glFeatures.restructuredMrWidget ? undefined : 'issue-new'" + variant="confirm" + category="primary" + @click="jumpToFirstUnresolvedDiscussion" > - {{ s__('mrWidget|Create issue to resolve all threads') }} + {{ s__('mrWidget|Jump to first unresolved thread') }} </gl-button> - </div> - </div> + </template> + </state-container> </template> 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 5bd7745d704..cf7f83c014a 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 @@ -12,13 +12,13 @@ import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_va import getStateQuery from '../../queries/get_state.query.graphql'; import draftQuery from '../../queries/states/draft.query.graphql'; import removeDraftMutation from '../../queries/toggle_draft.mutation.graphql'; -import StatusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'WorkInProgress', components: { - StatusIcon, GlButton, + StateContainer, }, mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { @@ -163,29 +163,22 @@ export default { </script> <template> - <div class="mr-widget-body media"> - <status-icon :show-disabled-button="canUpdate" status="warning" /> - <div class="media-body"> - <div class="float-left"> - <span - :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" - class="gl-font-weight-bold" - > - {{ - __("Merge blocked: merge request must be marked as ready. It's still marked as draft.") - }} - </span> - </div> + <state-container status="warning"> + <span class="gl-font-weight-bold gl-ml-0! gl-text-body! gl-flex-grow-1"> + {{ __("Merge blocked: merge request must be marked as ready. It's still marked as draft.") }} + </span> + <template #actions> <gl-button v-if="canUpdate" size="small" :disabled="isMakingRequest" :loading="isMakingRequest" - class="js-remove-draft gl-ml-3" + variant="confirm" + class="js-remove-draft gl-md-ml-3 gl-align-self-start" @click="handleRemoveDraft" > {{ s__('mrWidget|Mark as ready') }} </gl-button> - </div> - </div> + </template> + </state-container> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue new file mode 100644 index 00000000000..f1c1bde256f --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue @@ -0,0 +1,27 @@ +<script> +export default { + props: { + mr: { + type: Object, + required: true, + }, + }, + computed: { + widgets() { + return [].filter((w) => w); + }, + }, +}; +</script> + +<template> + <section role="region" :aria-label="__('Merge request reports')" data-testid="mr-widget-app"> + <component + :is="widget" + v-for="(widget, index) in widgets" + :key="widget.name || index" + :mr="mr" + :class="{ 'mr-widget-border-top': index === 0 }" + /> + </section> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue new file mode 100644 index 00000000000..9c8819327e6 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue @@ -0,0 +1,158 @@ +<script> +import * as Sentry from '@sentry/browser'; +import { normalizeHeaders } from '~/lib/utils/common_utils'; +import { __ } from '~/locale'; +import Poll from '~/lib/utils/poll'; +import StatusIcon from '../extensions/status_icon.vue'; +import { EXTENSION_ICON_NAMES } from '../../constants'; + +const FETCH_TYPE_COLLAPSED = 'collapsed'; + +export default { + components: { + StatusIcon, + }, + props: { + /** + * @param {value.collapsed} Object + * @param {value.extended} Object + */ + value: { + type: Object, + required: true, + }, + loadingText: { + type: String, + required: false, + default: __('Loading'), + }, + errorText: { + type: String, + required: false, + default: __('Failed to load'), + }, + fetchCollapsedData: { + type: Function, + required: true, + }, + fetchExtendedData: { + type: Function, + required: false, + default: undefined, + }, + // If the summary slot is not used, this value will be used as a fallback. + summary: { + type: String, + required: false, + default: undefined, + }, + // If the content slot is not used, this value will be used as a fallback. + content: { + type: Object, + required: false, + default: undefined, + }, + multiPolling: { + type: Boolean, + required: false, + default: false, + }, + statusIconName: { + type: String, + default: 'neutral', + required: false, + validator: (value) => Object.keys(EXTENSION_ICON_NAMES).indexOf(value) > -1, + }, + widgetName: { + type: String, + required: true, + }, + }, + data() { + return { + isLoading: false, + error: null, + }; + }, + watch: { + isLoading(newValue) { + this.$emit('is-loading', newValue); + }, + }, + async mounted() { + this.isLoading = true; + + try { + await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED); + } catch { + this.error = this.errorText; + } + + this.isLoading = false; + }, + methods: { + fetch(handler, dataType) { + const requests = this.multiPolling ? handler() : [handler]; + + const promises = requests.map((request) => { + return new Promise((resolve, reject) => { + const poll = new Poll({ + resource: { + fetchData: () => request(), + }, + method: 'fetchData', + successCallback: (response) => { + const headers = normalizeHeaders(response.headers); + + if (headers['POLL-INTERVAL']) { + return; + } + + resolve(response.data); + }, + errorCallback: (e) => { + Sentry.captureException(e); + reject(e); + }, + }); + + poll.makeRequest(); + }); + }); + + return Promise.all(promises).then((data) => { + this.$emit('input', { ...this.value, [dataType]: this.multiPolling ? data : data[0] }); + }); + }, + }, +}; +</script> + +<template> + <section class="media-section" data-testid="widget-extension"> + <div class="media gl-p-5"> + <status-icon + :level="1" + :name="widgetName" + :is-loading="isLoading" + :icon-name="statusIconName" + /> + <div + class="media-body gl-display-flex gl-flex-direction-row! gl-align-self-center" + data-testid="widget-extension-top-level" + > + <div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary"> + <slot name="summary">{{ isLoading ? loadingText : summary }}</slot> + </div> + <!-- actions will go here --> + <!-- toggle button will go here --> + </div> + </div> + <div + class="mr-widget-grouped-section gl-relative" + data-testid="widget-extension-collapsed-section" + > + <slot name="content">{{ content }}</slot> + </div> + </section> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js index 22e907f7e48..0fb5e13ad82 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js @@ -6,7 +6,6 @@ import { EXTENSION_ICONS } from '../../constants'; export default { name: 'WidgetAccessibility', enablePolling: true, - telemetry: false, i18n: { loading: s__('Reports|Accessibility scanning results are being parsed'), error: s__('Reports|Accessibility scanning failed loading results'), @@ -76,9 +75,9 @@ export default { return sprintf(s__('AccessibilityReport|Message: %{message}'), { message }); }, prepareReports() { - const { new_errors, existing_errors, resolved_errors } = this.collapsedData; + const { collapsedData } = this; - const newErrors = new_errors.map((error) => { + const newErrors = collapsedData.new_errors.map((error) => { return { header: __('New'), id: uniqueId('new-error-'), @@ -92,7 +91,7 @@ export default { }; }); - const existingErrors = existing_errors.map((error) => { + const existingErrors = collapsedData.existing_errors.map((error) => { return { id: uniqueId('existing-error-'), text: this.formatText(error.code), @@ -105,7 +104,7 @@ export default { }; }); - const resolvedErrors = resolved_errors.map((error) => { + const resolvedErrors = collapsedData.resolved_errors.map((error) => { return { id: uniqueId('resolved-error-'), text: this.formatText(error.code), diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js index 4ffd06de61f..6896f8831e8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js @@ -51,14 +51,14 @@ export const recentFailuresTextBuilder = (summary = {}) => { return i18n.recentFailureSummary(recentlyFailed, failed); }; -export const reportSubTextBuilder = ({ suite_errors, summary }) => { - if (suite_errors?.head || suite_errors?.base) { +export const reportSubTextBuilder = ({ suite_errors: suiteErrors, summary }) => { + if (suiteErrors?.head || suiteErrors?.base) { const errors = []; - if (suite_errors?.head) { - errors.push(`${i18n.headReportParsingError} ${suite_errors.head}`); + if (suiteErrors?.head) { + errors.push(`${i18n.headReportParsingError} ${suiteErrors.head}`); } - if (suite_errors?.base) { - errors.push(`${i18n.baseReportParsingError} ${suite_errors.base}`); + if (suiteErrors?.base) { + errors.push(`${i18n.baseReportParsingError} ${suiteErrors.base}`); } return errors.join('<br />'); } 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 627ddb0445e..d964b4bacac 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 @@ -20,13 +20,6 @@ export default { this.mr.preventMerge, ); }, - shouldShowMergeControls() { - if (this.glFeatures.restructuredMrWidget) { - return this.restructuredWidgetShowMergeButtons; - } - - return this.isMergeAllowed || this.isAutoMergeAvailable; - }, mergeDisabledText() { if (this.pipeline?.status === PIPELINE_SKIPPED_STATUS) { return MERGE_DISABLED_SKIPPED_PIPELINE_TEXT; 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 3e0ac236fdf..1e25143e15c 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 @@ -1,7 +1,6 @@ <script> import { GlSafeHtmlDirective } from '@gitlab/ui'; import { isEmpty } from 'lodash'; -import securityReportExtension from 'ee_else_ce/vue_merge_request_widget/extensions/security_reports'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue'; import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service'; @@ -17,7 +16,6 @@ import { setFaviconOverlay } from '../lib/utils/favicon'; import Loading from './components/loading.vue'; import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue'; import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue'; -import WidgetRelatedLinks from './components/mr_widget_related_links.vue'; import WidgetSuggestPipeline from './components/mr_widget_suggest_pipeline.vue'; import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue'; import ArchivedState from './components/states/mr_widget_archived.vue'; @@ -40,6 +38,7 @@ import ShaMismatch from './components/states/sha_mismatch.vue'; import UnresolvedDiscussionsState from './components/states/unresolved_discussions.vue'; import WorkInProgressState from './components/states/work_in_progress.vue'; import ExtensionsContainer from './components/extensions/container'; +import WidgetContainer from './components/widget/app.vue'; import { STATE_MACHINE, stateToComponentMap } from './constants'; import eventHub from './event_hub'; import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables'; @@ -59,9 +58,9 @@ export default { components: { Loading, ExtensionsContainer, + WidgetContainer, 'mr-widget-suggest-pipeline': WidgetSuggestPipeline, MrWidgetPipelineContainer, - 'mr-widget-related-links': WidgetRelatedLinks, MrWidgetAlertMessage, 'mr-widget-merged': MergedState, 'mr-widget-closed': ClosedState, @@ -73,9 +72,7 @@ export default { 'mr-widget-nothing-to-merge': NothingToMergeState, 'mr-widget-not-allowed': NotAllowedState, 'mr-widget-missing-branch': MissingBranchState, - 'mr-widget-ready-to-merge': window.gon?.features?.restructuredMrWidget - ? () => import('./components/states/new_ready_to_merge.vue') - : ReadyToMergeState, + 'mr-widget-ready-to-merge': () => import('./components/states/new_ready_to_merge.vue'), 'sha-mismatch': ShaMismatch, 'mr-widget-checking': CheckingState, 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, @@ -163,12 +160,6 @@ export default { shouldRenderCodeQuality() { return this.mr?.codequalityReportsPath; }, - shouldRenderRelatedLinks() { - return ( - (Boolean(this.mr.relatedLinks) || this.mr.divergedCommitsCount > 0) && - !this.mr.isNothingToMergeState - ); - }, shouldRenderSourceBranchRemovalStatus() { return ( !this.mr.canRemoveSourceBranch && @@ -239,9 +230,6 @@ export default { shouldShowCodeQualityExtension() { return window.gon?.features?.refactorCodeQualityExtension; }, - isRestructuredMrWidgetEnabled() { - return window.gon?.features?.restructuredMrWidget; - }, }, watch: { 'mr.machineValue': { @@ -275,11 +263,6 @@ export default { this.registerTestReportExtension(); } }, - shouldRenderSecurityReport(newVal) { - if (newVal) { - this.registerSecurityReportExtension(); - } - }, }, mounted() { MRWidgetService.fetchInitialData() @@ -535,11 +518,6 @@ export default { registerExtension(testReportExtension); } }, - registerSecurityReportExtension() { - if (this.shouldRenderSecurityReport && this.shouldShowSecurityExtension) { - registerExtension(securityReportExtension); - } - }, }, }; </script> @@ -600,7 +578,11 @@ export default { </template> </mr-widget-alert-message> </div> + <extensions-container :mr="mr" /> + + <widget-container v-if="mr" :mr="mr" /> + <grouped-codequality-reports-app v-if="shouldRenderCodeQuality && !shouldShowCodeQualityExtension" :head-blob-path="mr.headBlobPath" @@ -638,23 +620,7 @@ export default { <div class="mr-widget-section" data-qa-selector="mr_widget_content"> <component :is="componentName" :mr="mr" :service="service" /> - <ready-to-merge - v-if="isRestructuredMrWidgetEnabled && mr.commitsCount" - :mr="mr" - :service="service" - /> - <div v-else class="mr-widget-info"> - <mr-widget-related-links - v-if="shouldRenderRelatedLinks" - :state="mr.state" - :related-links="mr.relatedLinks" - :diverged-commits-count="mr.divergedCommitsCount" - :target-branch-path="mr.targetBranchPath" - class="mr-info-list gl-ml-7 gl-pb-5" - /> - - <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> - </div> + <ready-to-merge v-if="mr.commitsCount" :mr="mr" :service="service" /> </div> </div> <mr-widget-pipeline-container 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 index efc0673bc26..54770e6579a 100644 --- 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 @@ -1,11 +1,9 @@ fragment ReadyToMerge on Project { - __typename id onlyAllowMergeIfPipelineSucceeds mergeRequestsFfOnlyEnabled squashReadOnly mergeRequest(iid: $iid) { - __typename id autoMergeEnabled shouldRemoveSourceBranch diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js index 18d955652ba..7a458f9ce7e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js @@ -27,8 +27,6 @@ export default function deviseState() { return stateKey.shaMismatch; } else if (this.autoMergeEnabled && !this.mergeError) { return stateKey.autoMergeEnabled; - } else if (!this.canMerge && !window.gon?.features?.restructuredMrWidget) { - return stateKey.notAllowedToMerge; } else if (this.canBeMerged) { return stateKey.readyToMerge; } 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 03c9a01cc7a..146cf7e11a7 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 @@ -33,7 +33,6 @@ export default class MergeRequestStore { this.setData(data); this.initCodeQualityReport(data); - this.initSecurityReport(data); this.setGitpodData(data); } @@ -42,19 +41,6 @@ export default class MergeRequestStore { this.codeQuality = data.codequality_reports_path; } - initSecurityReport(data) { - // TODO: check if gl.mrWidgetData can be safely removed after we migrate to the - // widget extension. - this.securityReportPaths = { - apiFuzzingReportPath: data.api_fuzzing_comparison_path, - coverageFuzzingReportPath: data.coverage_fuzzing_comparison_path, - sastReportPath: data.sast_comparison_path, - dastReportPath: data.dast_comparison_path, - secretDetectionReportPath: data.secret_detection_comparison_path, - dependencyScanningReportPath: data.dependency_scanning_comparison_path, - }; - } - setData(data, isRebased) { this.initApprovals(); |