diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components')
34 files changed, 387 insertions, 209 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue index 38f40e8a3c8..30a0e7c383c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue @@ -63,6 +63,12 @@ export default { return btn.tooltipText; }, + actionButtonQaSelector(btn) { + if (btn.dataQaSelector) { + return btn.dataQaSelector; + } + return 'mr_widget_extension_actions_button'; + }, }, }; </script> @@ -105,7 +111,7 @@ export default { :target="btn.target" :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]" :data-clipboard-text="btn.dataClipboardText" - :data-qa-selector="btn.dataQaSelector" + :data-qa-selector="actionButtonQaSelector(btn)" :data-method="btn.dataMethod" :icon="btn.icon" :data-testid="btn.testId || 'extension-actions-button'" 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 254b280bf14..f377a185879 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 @@ -1,5 +1,5 @@ <script> -import { GlSprintf } from '@gitlab/ui'; +import { GlSprintf, GlLink } from '@gitlab/ui'; import { escape } from 'lodash'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { n__, s__, sprintf } from '~/locale'; @@ -9,6 +9,7 @@ const mergeCommitCount = s__('mrWidgetCommitsAdded|%{strongStart}1%{strongEnd} m export default { components: { GlSprintf, + GlLink, }, mixins: [glFeatureFlagMixin()], props: { @@ -40,6 +41,11 @@ export default { required: false, default: '', }, + mergeCommitPath: { + type: String, + required: false, + default: '', + }, }, computed: { isMerged() { @@ -124,7 +130,9 @@ export default { </template> </template> <template #mergeCommitSha> - <span class="label-branch">{{ mergeCommitSha }}</span> + <gl-link :href="mergeCommitPath" class="label-branch" data-testid="merge-commit-sha">{{ + mergeCommitSha + }}</gl-link> </template> </gl-sprintf> </span> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue index b1c4f7c5a7c..d7255eb6ad2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue @@ -103,7 +103,7 @@ export default { <span v-if="approvalLeftMessage">{{ message }}</span> <span v-else class="gl-font-weight-bold">{{ message }}</span> <user-avatar-list - class="gl-display-inline-block gl-vertical-align-middle" + class="gl-display-inline-block gl-vertical-align-middle gl-pt-1" :img-size="24" :items="approvers" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue index e115710b5d1..30098f7619a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue @@ -74,16 +74,12 @@ export default { <div class="js-deployment-info deployment-info"> <template v-if="hasDeploymentMeta"> <span>{{ deployedText }}</span> - <tooltip-on-truncate - :title="deployment.name" - truncate-target="child" - class="deploy-link label-truncate" - > + <tooltip-on-truncate :title="deployment.name" truncate-target="child" class="label-truncate"> <gl-link :href="deployment.url" target="_blank" rel="noopener noreferrer nofollow" - class="js-deploy-meta gl-font-sm" + class="js-deploy-meta gl-font-sm gl-pb-1" > {{ deployment.name }} </gl-link> 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 414c5bf9691..300e2a672cb 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 @@ -13,6 +13,7 @@ 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 StateContainer from '../state_container.vue'; import StatusIcon from './status_icon.vue'; import ChildContent from './child_content.vue'; import { createTelemetryHub } from './telemetry'; @@ -36,6 +37,7 @@ export default { ChildContent, DynamicScroller, DynamicScrollerItem, + StateContainer, }, directives: { SafeHtml: GlSafeHtmlDirective, @@ -307,19 +309,20 @@ export default { </script> <template> - <section class="media-section" data-testid="widget-extension"> - <div + <section + class="media-section" + data-testid="widget-extension" + data-qa-selector="mr_widget_extension" + > + <state-container + :mr="mr" + :status="statusIconName" + :is-loading="isLoadingSummary" :class="{ 'gl-cursor-pointer': isCollapsible }" - class="media gl-p-5" + class="gl-p-5" @mousedown="onRowMouseDown" @mouseup="onRowMouseUp" > - <status-icon - :level="1" - :name="$options.label || $options.name" - :is-loading="isLoadingSummary" - :icon-name="statusIconName" - /> <div class="media-body gl-display-flex gl-flex-direction-row! gl-align-self-center" data-testid="widget-extension-top-level" @@ -352,12 +355,13 @@ export default { :icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'" category="tertiary" data-testid="toggle-button" + data-qa-selector="toggle_button" size="small" @click="toggleCollapsed" /> </div> </div> - </div> + </state-container> <div v-if="!isCollapsed" class="mr-widget-grouped-section gl-relative" 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 1eccc7de660..52c9f047b76 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 @@ -62,7 +62,9 @@ export default { <strong v-else v-safe-html="generateText(data.header)"></strong> </div> <div class="gl-display-flex"> - <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" /> + <div v-if="data.icon" class="report-block-child-icon gl-display-flex"> + <status-icon :icon-name="data.icon.name" :size="12" class="gl-m-auto" /> + </div> <div class="gl-w-full"> <div class="gl-display-flex gl-flex-nowrap"> <div class="gl-flex-wrap gl-display-flex gl-w-full"> @@ -109,6 +111,7 @@ export default { :modal-id="modalId" :level="3" data-testid="child-content" + data-qa-selector="child_content" @clickedAction="onClickedAction" /> </li> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue index dc748ba44f2..f9d0986d60d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue @@ -49,18 +49,28 @@ export default { <div :class="[ $options.EXTENSION_ICON_CLASS[iconName], - { 'mr-widget-extension-icon gl-w-6': !isLoading && level === 1 }, + { 'gl-w-6': !isLoading && level === 1 }, { 'gl-p-2': isLoading || level === 1 }, ]" - class="gl-rounded-full gl-mr-3 gl-relative gl-p-2" + class="gl-mr-3 gl-p-2" > - <gl-loading-icon v-if="isLoading" size="sm" inline class="gl-display-block" /> - <gl-icon - v-else - :name="$options.EXTENSION_ICON_NAMES[iconName]" - :size="size" - :aria-label="iconAriaLabel" - class="gl-display-block" - /> + <div + class="gl-rounded-full gl-relative gl-display-flex" + :class="{ 'mr-widget-extension-icon': !isLoading && level === 1 }" + > + <div class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"> + <div class="gl-display-flex gl-m-auto gl-translate-y-n50"> + <gl-loading-icon v-if="isLoading" size="md" inline /> + <gl-icon + v-else + :name="$options.EXTENSION_ICON_NAMES[iconName]" + :size="size" + :aria-label="iconAriaLabel" + :data-qa-selector="`status_${iconName}_icon`" + class="gl-display-block" + /> + </div> + </div> + </div> </div> </template> 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 bc84459e298..d67ff11f297 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 @@ -24,7 +24,7 @@ const nonStandardEvents = { }, issues: { uniqueUser: { - expand: ['i_testing_load_performance_widget_total'], + expand: ['i_testing_issues_widget_total'], }, counter: {}, }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue index 437342bf438..0c36e1ccd7f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue @@ -13,7 +13,7 @@ export default { </script> <template> - <div class="circle-icon-container gl-mr-3 align-self-start"> + <div class="circle-icon-container gl-mr-3 align-self-start gl-mt-2"> <gl-icon :name="name" :size="24" /> </div> </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 1e1a2049414..fe69e96bd87 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 @@ -9,11 +9,10 @@ import { GlTooltipDirective, GlSafeHtmlDirective, } from '@gitlab/ui'; -import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; import { s__, n__ } from '~/locale'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; -import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import { MT_MERGE_STRATEGY } from '../constants'; @@ -31,14 +30,11 @@ export default { PipelineMiniGraph, TimeAgoTooltip, TooltipOnTruncate, - LinkedPipelinesMiniList: () => - import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, directives: { GlTooltip: GlTooltipDirective, SafeHtml: GlSafeHtmlDirective, }, - mixins: [mrWidgetPipelineMixin], props: { pipeline: { type: Object, @@ -172,7 +168,7 @@ export default { </p> </template> <template v-else-if="!hasPipeline"> - <gl-loading-icon size="lg" /> + <gl-loading-icon size="md" /> <p class="gl-flex-grow-1 gl-display-flex gl-ml-3 gl-mb-0" data-testid="monitoring-pipeline-message" @@ -276,17 +272,15 @@ export default { </div> </div> <div> - <span class="gl-align-items-center gl-display-inline-flex mr-widget-pipeline-graph"> - <span class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell"> - <linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" /> - <pipeline-mini-graph - v-if="hasStages" - stages-class="mr-widget-pipeline-stages" - :stages="pipeline.details.stages" - :is-merge-train="isMergeTrain" - /> - </span> - <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" /> + <span class="gl-align-items-center gl-display-inline-flex"> + <pipeline-mini-graph + v-if="pipeline.details.stages" + :downstream-pipelines="pipeline.triggered" + :is-merge-train="isMergeTrain" + :stages="pipeline.details.stages" + :upstream-pipeline="pipeline.triggered_by" + stages-class="mr-widget-pipeline-stages" + /> <pipeline-artifacts :pipeline-id="pipeline.id" :artifacts="artifacts" class="gl-ml-3" /> </span> </div> 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 5b8acb4ebf8..3239285e53e 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,11 +1,11 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; -import ciIcon from '~/vue_shared/components/ci_icon.vue'; +import { GlIcon } from '@gitlab/ui'; +import StatusIcon from './extensions/status_icon.vue'; export default { components: { - ciIcon, - GlLoadingIcon, + StatusIcon, + GlIcon, }, props: { status: { @@ -17,22 +17,20 @@ export default { isLoading() { return this.status === 'loading'; }, - statusObj() { - return { - group: this.status, - icon: `status_${this.status}`, - }; - }, }, }; </script> <template> - <div class="gl-display-flex gl-align-self-start"> - <div class="square s24 h-auto d-flex-center gl-mr-3"> - <div v-if="isLoading" class="mr-widget-icon gl-display-inline-flex"> - <gl-loading-icon size="md" class="mr-loading-icon gl-display-inline-flex" /> - </div> - <ci-icon v-else :status="statusObj" :size="24" /> + <div class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"> + <div class="gl-display-flex gl-m-auto"> + <gl-icon v-if="status === 'merged'" name="merge" :size="16" class="gl-text-blue-500" /> + <gl-icon + v-else-if="status === 'closed'" + name="merge-request-close" + :size="16" + class="gl-text-red-500" + /> + <status-icon v-else :is-loading="isLoading" :icon-name="status" :level="1" class="gl-m-0!" /> </div> </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 index 4a5a03fb598..822c5a68093 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue @@ -1,13 +1,23 @@ <script> +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { __ } from '~/locale'; import StatusIcon from './mr_widget_status_icon.vue'; import Actions from './action_buttons.vue'; export default { components: { + GlButton, StatusIcon, Actions, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { + mr: { + type: Object, + required: true, + }, isLoading: { type: Boolean, required: false, @@ -24,30 +34,67 @@ export default { default: () => [], }, }, + i18n: { + expandDetailsTooltip: __('Expand merge details'), + collapseDetailsTooltip: __('Collapse merge details'), + }, + computed: { + wrapperClasses() { + if (this.status === 'merged') return 'gl-bg-blue-50'; + if (this.status === 'closed') return 'gl-bg-red-50'; + return null; + }, + }, }; </script> <template> - <div class="mr-widget-body media"> + <div class="mr-widget-body media" :class="wrapperClasses" v-on="$listeners"> <div v-if="isLoading" class="gl-w-full mr-conflict-loader"> - <slot name="loading"></slot> + <slot name="loading"> + <div class="gl-display-flex"> + <status-icon status="loading" /> + <div class="media-body"> + <slot></slot> + </div> + </div> + </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-display-flex gl-w-full"> + <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" + > + <slot name="actions"> + <actions v-if="actions.length" :tertiary-buttons="actions" /> + </slot> + </div> + </div> <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" + class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1" > - <slot name="actions"> - <actions v-if="actions.length" :tertiary-buttons="actions" /> - </slot> + <gl-button + v-gl-tooltip + :title=" + mr.mergeDetailsCollapsed + ? $options.i18n.expandDetailsTooltip + : $options.i18n.collapseDetailsTooltip + " + :icon="mr.mergeDetailsCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'" + category="tertiary" + size="small" + class="gl-vertical-align-top" + @click="() => mr.toggleMergeDetails()" + /> </div> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue index a45823823f0..e2a9caf5419 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue @@ -34,7 +34,7 @@ export default { <template> <div class="mr-widget-body media gl-flex-wrap"> - <status-icon status="warning" /> + <status-icon status="failed" /> <p class="media-body gl-m-0! gl-font-weight-bold gl-text-black-normal!"> {{ failedText }} </p> 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 f74826f95d3..79e878431ed 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,22 +1,24 @@ <script> -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetArchived', components: { - statusIcon, + StateContainer, + }, + props: { + mr: { + type: Object, + required: true, + }, }, }; </script> + <template> - <div class="mr-widget-body media"> - <div class="space-children"> - <status-icon status="warning" show-disabled-button /> - </div> - <div class="media-body"> - <span class="gl-ml-0! gl-text-body! bold"> - {{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }} - </span> - </div> - </div> + <state-container :mr="mr" status="failed"> + <span class="gl-font-weight-bold"> + {{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }} + </span> + </state-container> </template> 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 690acc9a6dc..3c6c2a44e70 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,5 +1,5 @@ <script> -import { GlSkeletonLoader, GlIcon, GlSprintf } from '@gitlab/ui'; +import { GlSkeletonLoader, 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'; @@ -28,7 +28,6 @@ export default { components: { MrWidgetAuthor, GlSkeletonLoader, - GlIcon, GlSprintf, StateContainer, }, @@ -151,7 +150,7 @@ export default { }; </script> <template> - <state-container status="scheduled" :is-loading="loading" :actions="actions"> + <state-container :mr="mr" 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" /> @@ -168,8 +167,5 @@ export default { </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" /> - </template> </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 b0cda85f361..39c56cbb93d 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 @@ -58,8 +58,8 @@ export default { }; </script> <template> - <state-container status="warning" :actions="actions"> - <span class="bold gl-ml-0!"> + <state-container :mr="mr" status="failed" :actions="actions"> + <span class="gl-font-weight-bold"> <template v-if="mergeError">{{ mergeError }}</template> {{ s__('mrWidget|This merge request failed to be merged automatically') }} </span> 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 e2d87d8d536..922075516f3 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,20 +1,23 @@ <script> -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetChecking', components: { - statusIcon, + StateContainer, + }, + props: { + mr: { + type: Object, + required: true, + }, }, }; </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! bold"> - {{ s__('mrWidget|Checking if merge request can be merged…') }} - </span> - </div> - </div> + <state-container :mr="mr" status="loading"> + <span class="gl-font-weight-bold"> + {{ s__('mrWidget|Checking if merge request can be merged…') }} + </span> + </state-container> </template> 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 61f7d26f51e..806f8f939a6 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,16 +1,14 @@ <script> import MrWidgetAuthorTime from '../mr_widget_author_time.vue'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetClosed', components: { MrWidgetAuthorTime, - statusIcon, + StateContainer, }, props: { - /* TODO: This is providing all store and service down when it - only needs metrics and targetBranch */ mr: { type: Object, required: true, @@ -19,15 +17,12 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <status-icon status="warning" /> - <div class="media-body"> - <mr-widget-author-time - :action-text="s__('mrWidget|Closed by')" - :author="mr.metrics.closedBy" - :date-title="mr.metrics.closedAt" - :date-readable="mr.metrics.readableClosedAt" - /> - </div> - </div> + <state-container :mr="mr" status="closed"> + <mr-widget-author-time + :action-text="s__('mrWidget|Closed by')" + :author="mr.metrics.closedBy" + :date-title="mr.metrics.closedAt" + :date-readable="mr.metrics.readableClosedAt" + /> + </state-container> </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 8abd915b93e..d60d3cfc9ea 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 @@ -86,7 +86,7 @@ export default { }; </script> <template> - <state-container status="warning" :is-loading="isLoading"> + <state-container :mr="mr" status="failed" :is-loading="isLoading"> <template #loading> <gl-skeleton-loader :width="334" :height="30"> <rect x="0" y="7" width="150" height="16" rx="4" /> 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 18103ac4a0e..8a7f15d8d1a 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 @@ -1,16 +1,14 @@ <script> -import { GlButton } from '@gitlab/ui'; import { stripHtml } from '~/lib/utils/text_utility'; import { sprintf, s__, n__ } from '~/locale'; import eventHub from '../../event_hub'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StateContainer from '../state_container.vue'; export default { name: 'MRWidgetFailedToMerge', components: { - GlButton, - statusIcon, + StateContainer, }, props: { @@ -47,6 +45,16 @@ export default { this.timer, ); }, + actions() { + return [ + { + text: s__('mrWidget|Refresh now'), + onClick: () => this.refresh(), + testId: 'merge-request-failed-refresh-button', + dataQaSelector: 'merge_request_error_content', + }, + ]; + }, }, mounted() { @@ -87,30 +95,18 @@ export default { }; </script> <template> - <div class="mr-widget-body media"> - <template v-if="isRefreshing"> - <status-icon status="loading" /> - <span class="media-body bold js-refresh-label"> {{ s__('mrWidget|Refreshing now') }} </span> - </template> - <template v-else> - <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" data-testid="merge-error"> - {{ mergeError }} - </span> - <span v-else> {{ s__('mrWidget|Merge failed.') }} </span> - <span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span> - </span> - <gl-button - size="small" - data-testid="merge-request-failed-refresh-button" - data-qa-selector="merge_request_error_content" - @click="refresh" - > - {{ s__('mrWidget|Refresh now') }} - </gl-button> - </div> - </template> - </div> + <state-container v-if="isRefreshing" :mr="mr" status="loading"> + <span class="gl-font-weight-bold"> + {{ s__('mrWidget|Refreshing now') }} + </span> + </state-container> + <state-container v-else :mr="mr" status="failed" :actions="actions"> + <span class="gl-font-weight-bold"> + <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> + </state-container> </template> 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 4416123cd51..e9298b0c856 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,5 +1,5 @@ <script> -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { GlTooltipDirective } from '@gitlab/ui'; import api from '~/api'; import createFlash from '~/flash'; import { s__, __ } from '~/locale'; @@ -16,7 +16,6 @@ export default { }, components: { MrWidgetAuthorTime, - GlIcon, StateContainer, }, props: { @@ -49,18 +48,6 @@ export default { const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr; return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest); }, - shouldShowMergedButtons() { - const { - canRevertInCurrentMR, - canCherryPickInCurrentMR, - revertInForkPath, - cherryPickInForkPath, - } = this.mr; - - return ( - canRevertInCurrentMR || canCherryPickInCurrentMR || revertInForkPath || cherryPickInForkPath - ); - }, revertTitle() { return s__('mrWidget|Revert this merge request in a new merge request'); }, @@ -163,10 +150,7 @@ export default { }; </script> <template> - <state-container :actions="actions"> - <template #icon> - <gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" /> - </template> + <state-container :mr="mr" :actions="actions" status="merged"> <mr-widget-author-time :action-text="s__('mrWidget|Merged by')" :author="mr.metrics.mergedBy" 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 c7574a41bb8..51ac2576f75 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 @@ -4,7 +4,7 @@ import simplePoll from '~/lib/utils/simple_poll'; import MergeRequest from '~/merge_request'; import eventHub from '../../event_hub'; import { MERGE_ACTIVE_STATUS_PHRASES, STATE_MACHINE } from '../../constants'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; const { transitions } = STATE_MACHINE; const { MERGE_FAILURE } = transitions; @@ -12,7 +12,7 @@ const { MERGE_FAILURE } = transitions; export default { name: 'MRWidgetMerging', components: { - statusIcon, + StatusIcon, }, props: { mr: { 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 659d12d1160..214d1b49732 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 @@ -9,7 +9,7 @@ import { MR_WIDGET_MISSING_BRANCH_RESTORE, MR_WIDGET_MISSING_BRANCH_MANUALCLI, } from '../../i18n'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; export default { name: 'MRWidgetMissingBranch', @@ -19,7 +19,7 @@ export default { components: { GlIcon, GlSprintf, - statusIcon, + StatusIcon, }, mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { @@ -71,10 +71,10 @@ export default { </script> <template> <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="warning" /> + <status-icon :show-disabled-button="true" status="failed" /> <div class="media-body space-children"> - <span class="gl-ml-0! gl-text-body! bold js-branch-text" data-testid="widget-content"> + <span class="gl-font-weight-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_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue index c203d2824fa..d837551a813 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue @@ -11,9 +11,9 @@ export default { <template> <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="success" /> + <status-icon status="success" /> <div class="media-body space-children"> - <span class="bold"> + <span class="gl-font-weight-bold"> {{ s__(`mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request`) 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 e99ee59b877..13920daca15 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 @@ -12,9 +12,9 @@ export default { </script> <template> <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="warning" /> + <status-icon status="failed" /> <div class="media-body space-children"> - <span class="gl-ml-0! gl-text-body! bold"> + <span class="gl-font-weight-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 6c5fc916799..37c8d5d15f3 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 @@ -81,16 +81,19 @@ export default { return 'loading'; } if (!this.canPushToSourceBranch && !this.rebaseInProgress) { - return 'warning'; + return 'failed'; } return 'success'; }, - showDisabledButton() { - return ['failed', 'loading'].includes(this.status); - }, fastForwardMergeText() { return __('Merge blocked: the source branch must be rebased onto the target branch.'); }, + showRebaseWithoutPipeline() { + return ( + !this.mr.onlyAllowMergeIfPipelineSucceeds || + (this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.allowMergeOnSkippedPipeline) + ); + }, }, methods: { rebase({ skipCi = false } = {}) { @@ -149,7 +152,7 @@ export default { }; </script> <template> - <state-container :status="status" :is-loading="isLoading"> + <state-container :mr="mr" :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" /> @@ -192,6 +195,7 @@ export default { </template> <template v-if="!isLoading" #actions> <gl-button + v-if="showRebaseWithoutPipeline" :loading="isMakingRequest" variant="confirm" size="small" 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 d507e5f232b..3cbd171a035 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 @@ -2,14 +2,14 @@ import { GlLink, GlSprintf } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; -import statusIcon from '../mr_widget_status_icon.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; export default { name: 'PipelineFailed', components: { GlLink, GlSprintf, - statusIcon, + StatusIcon, }, computed: { troubleshootingDocsPath() { @@ -26,9 +26,9 @@ export default { <template> <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="warning" /> + <status-icon status="failed" /> <div class="media-body space-children"> - <span class="gl-ml-0! gl-text-body! bold"> + <span class="gl-font-weight-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 d2c85b14999..78430abcfe9 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 @@ -680,6 +680,7 @@ export default { :is-fast-forward-enabled="!shouldShowMergeEdit" :commits-count="commitsCount" :target-branch="stateData.targetBranch" + :merge-commit-path="mr.mergeCommitPath" /> </li> <li v-if="mr.state !== 'closed'" class="gl-line-height-normal"> 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 d149f5208fc..27919f90cc3 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 @@ -22,7 +22,7 @@ export default { </script> <template> - <state-container status="warning"> + <state-container :mr="mr" status="failed"> <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" 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 035d62eaa59..8f2e4eb2131 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 @@ -24,7 +24,7 @@ export default { </script> <template> - <state-container status="warning"> + <state-container :mr="mr" status="failed"> <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!" > 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 cf7f83c014a..0458e9dfaf5 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 @@ -163,7 +163,7 @@ export default { </script> <template> - <state-container status="warning"> + <state-container :mr="mr" status="failed"> <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> 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 index f1c1bde256f..2f52ac70833 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue @@ -15,7 +15,12 @@ export default { </script> <template> - <section role="region" :aria-label="__('Merge request reports')" data-testid="mr-widget-app"> + <section + v-if="widgets.length" + role="region" + :aria-label="__('Merge request reports')" + data-testid="mr-widget-app" + > <component :is="widget" v-for="(widget, index) in widgets" 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 index 9c8819327e6..c9fc2dde0bd 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue @@ -1,21 +1,32 @@ <script> +import { GlButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import { normalizeHeaders } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; +import { sprintf, __ } from '~/locale'; import Poll from '~/lib/utils/poll'; import StatusIcon from '../extensions/status_icon.vue'; -import { EXTENSION_ICON_NAMES } from '../../constants'; +import ActionButtons from '../action_buttons.vue'; +import { EXTENSION_ICONS } from '../../constants'; +import ContentSection from './widget_content_section.vue'; const FETCH_TYPE_COLLAPSED = 'collapsed'; +const FETCH_TYPE_EXPANDED = 'expanded'; export default { components: { + ActionButtons, StatusIcon, + GlButton, + GlLoadingIcon, + ContentSection, + }, + directives: { + GlTooltip: GlTooltipDirective, }, props: { /** * @param {value.collapsed} Object - * @param {value.extended} Object + * @param {value.expanded} Object */ value: { type: Object, @@ -35,7 +46,7 @@ export default { type: Function, required: true, }, - fetchExtendedData: { + fetchExpandedData: { type: Function, required: false, default: undefined, @@ -61,7 +72,16 @@ export default { type: String, default: 'neutral', required: false, - validator: (value) => Object.keys(EXTENSION_ICON_NAMES).indexOf(value) > -1, + validator: (value) => Object.keys(EXTENSION_ICONS).indexOf(value) > -1, + }, + isCollapsible: { + type: Boolean, + required: true, + }, + actionButtons: { + type: Array, + required: false, + default: () => [], }, widgetName: { type: String, @@ -70,10 +90,22 @@ export default { }, data() { return { + isExpandedForTheFirstTime: true, + isCollapsed: true, isLoading: false, - error: null, + isLoadingExpandedContent: false, + summaryError: null, + contentError: null, }; }, + computed: { + collapseButtonLabel() { + return sprintf(this.isCollapsed ? __('Show details') : __('Hide details')); + }, + summaryStatusIcon() { + return this.summaryError ? this.$options.failedStatusIcon : this.statusIconName; + }, + }, watch: { isLoading(newValue) { this.$emit('is-loading', newValue); @@ -85,12 +117,36 @@ export default { try { await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED); } catch { - this.error = this.errorText; + this.summaryError = this.errorText; } this.isLoading = false; }, methods: { + toggleCollapsed() { + this.isCollapsed = !this.isCollapsed; + + if (this.isExpandedForTheFirstTime && typeof this.fetchExpandedData === 'function') { + this.isExpandedForTheFirstTime = false; + this.fetchExpandedContent(); + } + }, + async fetchExpandedContent() { + this.isLoadingExpandedContent = true; + this.contentError = null; + + try { + await this.fetch(this.fetchExpandedData, FETCH_TYPE_EXPANDED); + } catch { + this.contentError = this.errorText; + + // Reset these values so that we allow refetching + this.isExpandedForTheFirstTime = true; + this.isCollapsed = true; + } + + this.isLoadingExpandedContent = false; + }, fetch(handler, dataType) { const requests = this.multiPolling ? handler() : [handler]; @@ -125,6 +181,7 @@ export default { }); }, }, + failedStatusIcon: EXTENSION_ICONS.failed, }; </script> @@ -135,24 +192,58 @@ export default { :level="1" :name="widgetName" :is-loading="isLoading" - :icon-name="statusIconName" + :icon-name="summaryStatusIcon" /> <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> + <span v-if="summaryError">{{ summaryError }}</span> + <slot v-else name="summary">{{ isLoading ? loadingText : summary }}</slot> + </div> + <action-buttons + v-if="actionButtons.length > 0" + :widget="widgetName" + :tertiary-buttons="actionButtons" + /> + <div + v-if="isCollapsible" + class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6" + > + <gl-button + v-gl-tooltip + :title="collapseButtonLabel" + :aria-expanded="`${!isCollapsed}`" + :aria-label="collapseButtonLabel" + :icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'" + category="tertiary" + data-testid="toggle-button" + size="small" + @click="toggleCollapsed" + /> </div> - <!-- actions will go here --> - <!-- toggle button will go here --> </div> </div> <div + v-if="!isCollapsed || contentError" class="mr-widget-grouped-section gl-relative" data-testid="widget-extension-collapsed-section" > - <slot name="content">{{ content }}</slot> + <div v-if="isLoadingExpandedContent" class="report-block-container gl-text-center"> + <gl-loading-icon size="sm" inline /> {{ __('Loading...') }} + </div> + <content-section + v-else-if="contentError" + class="report-block-container" + :status-icon-name="$options.failedStatusIcon" + :widget-name="widgetName" + > + {{ contentError }} + </content-section> + <slot v-else name="content"> + {{ content }} + </slot> </div> </section> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_section.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_section.vue new file mode 100644 index 00000000000..61e3744b5dc --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_section.vue @@ -0,0 +1,35 @@ +<script> +import { EXTENSION_ICONS } from '../../constants'; +import StatusIcon from '../extensions/status_icon.vue'; + +export default { + components: { + StatusIcon, + }, + props: { + statusIconName: { + type: String, + default: '', + required: false, + validator: (value) => value === '' || Object.keys(EXTENSION_ICONS).includes(value), + }, + widgetName: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div class="gl-px-7"> + <div class="gl-pl-4 gl-display-flex"> + <status-icon + v-if="statusIconName" + :level="2" + :name="widgetName" + :icon-name="statusIconName" + /> + <slot name="default"></slot> + </div> + </div> +</template> |