Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue21
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/state_container.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue64
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue52
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue134
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/status_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue44
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue35
20 files changed, 374 insertions, 84 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 2cfeb7a4bcb..eb93f42e2f3 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
@@ -189,8 +189,11 @@ export default {
.then((data) => {
this.mr.setApprovals(data);
- eventHub.$emit('MRWidgetUpdateRequested');
- eventHub.$emit('ApprovalUpdated');
+ if (!window.gon?.features?.realtimeMrStatusChange) {
+ eventHub.$emit('MRWidgetUpdateRequested');
+ eventHub.$emit('ApprovalUpdated');
+ }
+
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 1256b3a8e52..c7d34d45f06 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
@@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui';
import { backOff } from '~/lib/utils/common_utils';
-import statusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
import { bytesToMiB } from '~/lib/utils/number_utils';
import { s__ } from '~/locale';
import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
@@ -107,7 +107,7 @@ export default {
backOff((next, stop) => {
MRWidgetService.fetchMetrics(this.metricsUrl)
.then((res) => {
- if (res.status === statusCodes.NO_CONTENT) {
+ if (res.status === HTTP_STATUS_NO_CONTENT) {
this.backOffRequestCounter += 1;
/* eslint-disable no-unused-expressions */
this.backOffRequestCounter < 3 ? next() : stop(res);
@@ -118,7 +118,7 @@ export default {
.catch(stop);
})
.then((res) => {
- if (res.status === statusCodes.NO_CONTENT) {
+ if (res.status === HTTP_STATUS_NO_CONTENT) {
return res;
}
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 3d03dbd9db3..e8cc9b2eb2a 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
@@ -1,12 +1,7 @@
<script>
-import {
- GlButton,
- GlLoadingIcon,
- GlSafeHtmlDirective,
- GlTooltipDirective,
- GlIntersectionObserver,
-} from '@gitlab/ui';
+import { GlButton, GlLoadingIcon, GlTooltipDirective, GlIntersectionObserver } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import { sprintf, s__, __ } from '~/locale';
import Poll from '~/lib/utils/poll';
@@ -40,7 +35,7 @@ export default {
StateContainer,
},
directives: {
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
GlTooltip: GlTooltipDirective,
},
data() {
@@ -323,19 +318,23 @@ export default {
@mouseup="onRowMouseUp"
>
<div
+ :class="{ 'gl-h-full': isLoadingSummary }"
class="media-body gl-display-flex gl-flex-direction-row! gl-w-full"
data-testid="widget-extension-top-level"
>
- <div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary">
+ <div
+ class="gl-flex-grow-1 gl-display-flex gl-align-items-center"
+ data-testid="widget-extension-top-level-summary"
+ >
<template v-if="isLoadingSummary">{{ widgetLoadingText }}</template>
<template v-else-if="hasFetchError">{{ widgetErrorText }}</template>
- <div v-else>
+ <template v-else>
<span v-safe-html="hydratedSummary.subject"></span>
<template v-if="hydratedSummary.meta">
<br />
<span v-safe-html="hydratedSummary.meta" class="gl-font-sm"></span>
</template>
- </div>
+ </template>
</div>
<actions
:widget="$options.label || $options.name"
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 a10e5efa0e7..fa369d23b6c 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,6 +1,7 @@
<script>
-import { GlBadge, GlLink, GlSafeHtmlDirective, GlModalDirective } from '@gitlab/ui';
+import { GlBadge, GlLink, GlModalDirective } from '@gitlab/ui';
import { isArray } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import Actions from '../action_buttons.vue';
import StatusIcon from './status_icon.vue';
import { generateText } from './utils';
@@ -14,7 +15,7 @@ export default {
Actions,
},
directives: {
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
GlModal: GlModalDirective,
},
props: {
@@ -97,7 +98,12 @@ export default {
<div v-if="data.supportingText">
<p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
</div>
- <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
+ <gl-badge
+ v-if="data.badge"
+ :variant="data.badge.variant || 'info'"
+ size="sm"
+ class="gl-ml-2"
+ >
{{ data.badge.text }}
</gl-badge>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index f71b1fbc539..79ea2624ec5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,8 +1,11 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
export default {
name: 'MrWidgetAuthor',
+ components: {
+ GlLink,
+ },
directives: {
GlTooltip: GlTooltipDirective,
},
@@ -28,13 +31,16 @@ export default {
};
</script>
<template>
- <a
+ <gl-link
v-gl-tooltip
:href="authorUrl"
:title="showAuthorName ? null : author.name"
- class="author-link inline"
+ class="mr-widget-author"
>
- <img :src="avatarUrl" class="avatar avatar-inline s16" />
- <span v-if="showAuthorName" class="author">{{ author.name }}</span>
- </a>
+ <img :src="avatarUrl" :alt="author.name" class="avatar avatar-inline s16" /><span
+ v-if="showAuthorName"
+ class="author"
+ >{{ author.name }}</span
+ >
+ </gl-link>
</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 97c6de37054..d8a361066f4 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
@@ -7,8 +7,8 @@ import {
GlSprintf,
GlTooltip,
GlTooltipDirective,
- GlSafeHtmlDirective,
} from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, n__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
@@ -33,7 +33,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
},
props: {
pipeline: {
@@ -190,7 +190,7 @@ export default {
</template>
<template v-else-if="hasPipeline">
<a :href="status.details_path" class="gl-align-self-center gl-mr-3">
- <ci-icon :status="status" :size="24" />
+ <ci-icon :status="status" :size="24" class="gl-display-flex" />
</a>
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
@@ -277,9 +277,9 @@ export default {
v-if="pipeline.details.stages"
:downstream-pipelines="pipeline.triggered"
:is-merge-train="isMergeTrain"
+ :pipeline-path="pipeline.path"
: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>
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 870972156c5..1fd1e264c25 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,5 +1,6 @@
<script>
-import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, n__ } from '~/locale';
export default {
@@ -54,16 +55,16 @@ export default {
</script>
<template>
<section>
- <p v-if="relatedLinks.closing" class="gl-display-inline gl-m-0">
+ <p v-if="relatedLinks.closing" class="gl-display-inline gl-m-0 gl-font-sm!">
{{ closesText }}
<span v-safe-html="relatedLinks.closing"></span>
</p>
- <p v-if="relatedLinks.mentioned" class="gl-display-inline gl-m-0">
+ <p v-if="relatedLinks.mentioned" class="gl-display-inline gl-m-0 gl-font-sm!">
<span v-if="relatedLinks.closing">&middot;</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">
+ <p v-if="shouldShowAssignToMeLink" class="gl-display-inline gl-m-0 gl-font-sm!">
<span>
<gl-link rel="nofollow" data-method="post" :href="relatedLinks.assignToMe">{{
assignIssueText
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 66e33a08a12..9a3555d3e11 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
@@ -54,7 +54,7 @@ export default {
<template>
<div
- class="mr-widget-body media mr-widget-body-line-height-1 gl-line-height-normal"
+ class="mr-widget-body media gl-display-flex gl-align-items-center"
:class="wrapperClasses"
v-on="$listeners"
>
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 38b99dae264..e5688091cc7 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
@@ -1,6 +1,6 @@
<script>
import { s__ } from '~/locale';
-import StatusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
import { DETAILED_MERGE_STATUS } from '../../constants';
export default {
@@ -12,7 +12,7 @@ export default {
externalStatusChecksFailed: s__('mrWidget|Merge blocked: all status checks must pass.'),
},
components: {
- StatusIcon,
+ StateContainer,
},
props: {
mr: {
@@ -37,10 +37,11 @@ export default {
</script>
<template>
- <div class="mr-widget-body media gl-flex-wrap">
- <status-icon status="failed" />
- <p class="media-body gl-m-0! gl-font-weight-bold gl-text-black-normal!">
+ <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!"
+ >
{{ failedText }}
- </p>
- </div>
+ </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 806f8f939a6..6bcf88713a5 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,7 +1,17 @@
<script>
+import api from '~/api';
+import showGlobalToast from '~/vue_shared/plugins/global_toast';
+
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
import StateContainer from '../state_container.vue';
+import {
+ MR_WIDGET_CLOSED_REOPEN,
+ MR_WIDGET_CLOSED_REOPENING,
+ MR_WIDGET_CLOSED_RELOADING,
+ MR_WIDGET_CLOSED_REOPEN_FAILURE,
+} from '../../i18n';
+
export default {
name: 'MRWidgetClosed',
components: {
@@ -14,10 +24,62 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ isPending: false,
+ isReloading: false,
+ };
+ },
+ computed: {
+ reopenText() {
+ let text = MR_WIDGET_CLOSED_REOPEN;
+
+ if (this.isPending) {
+ text = MR_WIDGET_CLOSED_REOPENING;
+ } else if (this.isReloading) {
+ text = MR_WIDGET_CLOSED_RELOADING;
+ }
+
+ return text;
+ },
+ actions() {
+ if (!window.gon?.current_user_id) {
+ return [];
+ }
+
+ return [
+ {
+ text: this.reopenText,
+ loading: this.isPending || this.isReloading,
+ onClick: this.reopen,
+ testId: 'extension-actions-reopen-button',
+ },
+ ];
+ },
+ },
+ methods: {
+ reopen() {
+ this.isPending = true;
+
+ api
+ .updateMergeRequest(this.mr.targetProjectId, this.mr.iid, { state_event: 'reopen' })
+ .then(() => {
+ this.isReloading = true;
+
+ window.location.reload();
+ })
+ .catch(() => {
+ showGlobalToast(MR_WIDGET_CLOSED_REOPEN_FAILURE);
+ })
+ .finally(() => {
+ this.isPending = false;
+ });
+ },
+ },
};
</script>
<template>
- <state-container :mr="mr" status="closed">
+ <state-container :mr="mr" status="closed" :actions="actions">
<mr-widget-author-time
:action-text="s__('mrWidget|Closed by')"
:author="mr.metrics.closedBy"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 4902c9b45e8..850a4e2fd56 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -1,5 +1,6 @@
<script>
-import { GlButton, GlSprintf, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
import api from '~/api';
import { helpPagePath } from '~/helpers/help_page_helper';
@@ -12,7 +13,7 @@ export default {
GlLink,
},
directives: {
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
},
props: {
mr: {
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 c54672cd0f8..23b163e2c6a 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
@@ -20,6 +20,8 @@ import simplePoll from '~/lib/utils/simple_poll';
import { __, s__, n__ } from '~/locale';
import SmartInterval from '~/smart_interval';
import { helpPagePath } from '~/helpers/help_page_helper';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import readyToMergeSubscription from '~/vue_merge_request_widget/queries/states/ready_to_merge.subscription.graphql';
import {
AUTO_MERGE_STRATEGIES,
WARNING,
@@ -87,6 +89,31 @@ export default {
this.initPolling();
}
},
+ subscribeToMore: {
+ document() {
+ return readyToMergeSubscription;
+ },
+ skip() {
+ return !this.mr?.id || this.loading || !window.gon?.features?.realtimeMrStatusChange;
+ },
+ variables() {
+ return {
+ issuableId: convertToGraphQLId('MergeRequest', this.mr?.id),
+ };
+ },
+ updateQuery(
+ _,
+ {
+ subscriptionData: {
+ data: { mergeRequestMergeStatusUpdated },
+ },
+ },
+ ) {
+ if (mergeRequestMergeStatusUpdated) {
+ this.state = mergeRequestMergeStatusUpdated;
+ }
+ },
+ },
},
},
components: {
@@ -295,7 +322,7 @@ export default {
return this.mr.divergedCommitsCount > 0;
},
showMergeDetailsHeader() {
- return ['readyToMerge'].indexOf(this.mr.state) >= 0;
+ return !['readyToMerge'].includes(this.mr.state);
},
},
mounted() {
@@ -467,8 +494,9 @@ export default {
<template>
<div
+ :class="{ 'gl-bg-gray-10': mr.state !== 'closed' && mr.state !== 'merged' }"
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"
+ class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-pl-7"
>
<div v-if="loading" class="mr-widget-body">
<div class="gl-w-full mr-ready-to-merge-loader">
@@ -481,7 +509,9 @@ export default {
</div>
</div>
<template v-else>
- <div class="mr-widget-body mr-widget-body-ready-merge media mr-widget-body-line-height-1">
+ <div
+ class="mr-widget-body mr-widget-body-ready-merge media gl-display-flex gl-align-items-center"
+ >
<div class="media-body">
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap">
<template v-if="shouldShowMergeControls">
@@ -555,7 +585,19 @@ export default {
</li>
</ul>
</div>
- <div class="gl-w-full gl-text-gray-500 gl-mb-3 gl-md-mb-0 gl-md-pb-5">
+ <div
+ class="gl-w-full gl-text-gray-500 gl-mb-3 gl-md-mb-0 gl-md-pb-5 mr-widget-merge-details"
+ >
+ <template v-if="sourceHasDivergedFromTarget">
+ <gl-sprintf :message="$options.i18n.sourceDivergedFromTargetText">
+ <template #link>
+ <gl-link :href="mr.targetBranchPath">{{
+ $options.i18n.divergedCommits(mr.divergedCommitsCount)
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ &middot;
+ </template>
<added-commit-message
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
@@ -631,7 +673,7 @@ export default {
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">
+ <p v-if="showMergeDetailsHeader" class="gl-mb-2 gl-text-gray-900">
{{ __('Merge details') }}
</p>
<ul class="gl-pl-4 gl-mb-0 gl-ml-3 gl-text-gray-600">
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 074758e33b2..9f3748599dc 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
@@ -26,7 +26,7 @@ export default {
<template>
<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! gl-align-self-start"
+ 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>
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 ef5be0fbfcd..01f9b4757a0 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
@@ -94,6 +94,7 @@ export default {
errors: [],
mergeRequest: {
__typename: 'MergeRequest',
+ id: this.mr.issuableId,
mergeableDiscussionsState: true,
title: this.mr.title,
draft: false,
@@ -111,7 +112,10 @@ export default {
}) => {
toast(__('Marked as ready. Merging is now allowed.'));
$('.merge-request .detail-page-description .title').text(title);
- eventHub.$emit('MRWidgetUpdateRequested');
+
+ if (!window.gon?.features?.realtimeMrStatusChange) {
+ eventHub.$emit('MRWidgetUpdateRequested');
+ }
},
)
.catch(() =>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue
new file mode 100644
index 00000000000..6655af92a55
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue
@@ -0,0 +1,134 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem, GlTooltipDirective } from '@gitlab/ui';
+import { sprintf, __ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ widget: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tertiaryButtons: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ data: () => {
+ return {
+ timeout: null,
+ updatingTooltip: false,
+ };
+ },
+ computed: {
+ dropdownLabel() {
+ if (!this.widget) return undefined;
+
+ return sprintf(__('%{widget} options'), { widget: this.widget });
+ },
+ },
+ methods: {
+ onClickAction(action) {
+ this.$emit('clickedAction', action);
+
+ if (action.onClick) {
+ action.onClick();
+ }
+
+ if (action.tooltipOnClick) {
+ this.updatingTooltip = true;
+ this.$root.$emit('bv::show::tooltip', action.id);
+
+ clearTimeout(this.timeout);
+
+ this.timeout = setTimeout(() => {
+ this.updatingTooltip = false;
+ this.$root.$emit('bv::hide::tooltip', action.id);
+ }, 1000);
+ }
+ },
+ setTooltip(btn) {
+ if (this.updatingTooltip && btn.tooltipOnClick) {
+ return btn.tooltipOnClick;
+ }
+
+ return btn.tooltipText;
+ },
+ actionButtonQaSelector(btn) {
+ if (btn.dataQaSelector) {
+ return btn.dataQaSelector;
+ }
+ return 'mr_widget_extension_actions_button';
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-flex-start">
+ <gl-dropdown
+ v-if="tertiaryButtons.length"
+ v-gl-tooltip
+ :title="__('Options')"
+ :text="dropdownLabel"
+ icon="ellipsis_v"
+ no-caret
+ category="tertiary"
+ right
+ lazy
+ text-sr-only
+ size="small"
+ toggle-class="gl-p-2!"
+ class="gl-display-block gl-md-display-none!"
+ >
+ <gl-dropdown-item
+ v-for="(btn, index) in tertiaryButtons"
+ :key="index"
+ :href="btn.href"
+ :target="btn.target"
+ :data-clipboard-text="btn.dataClipboardText"
+ :data-method="btn.dataMethod"
+ @click="onClickAction(btn)"
+ >
+ {{ btn.text }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ <template v-if="tertiaryButtons.length">
+ <gl-button
+ v-for="(btn, index) in tertiaryButtons"
+ :id="btn.id"
+ :key="index"
+ v-gl-tooltip.hover
+ :title="setTooltip(btn)"
+ :href="btn.href"
+ :target="btn.target"
+ :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]"
+ :data-clipboard-text="btn.dataClipboardText"
+ :data-qa-selector="actionButtonQaSelector(btn)"
+ :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"
+ @click="onClickAction(btn)"
+ >
+ <template v-if="btn.text">
+ {{ btn.text }}
+ </template>
+ </gl-button>
+ </template>
+ </div>
+</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
index 2f52ac70833..18aa85484ea 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
@@ -20,13 +20,14 @@ export default {
role="region"
:aria-label="__('Merge request reports')"
data-testid="mr-widget-app"
+ class="mr-widget-section"
>
<component
:is="widget"
v-for="(widget, index) in widgets"
:key="widget.name || index"
:mr="mr"
- :class="{ 'mr-widget-border-top': index === 0 }"
+ class="mr-widget-section"
/>
</section>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
index 4d66c75719b..cdce7c6625a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
@@ -1,8 +1,9 @@
<script>
-import { GlBadge, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
-import Actions from '../action_buttons.vue';
+import { GlBadge, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { generateText } from '../extensions/utils';
import ContentRow from './widget_content_row.vue';
+import Actions from './action_buttons.vue';
export default {
name: 'DynamicContent',
@@ -13,7 +14,7 @@ export default {
ContentRow,
},
directives: {
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
},
props: {
data: {
@@ -81,10 +82,8 @@ export default {
v-if="data.children && data.children.length > 0 && level === 2"
class="gl-m-0 gl-p-0 gl-list-style-none"
>
- <li>
+ <li v-for="(childData, index) in data.children" :key="childData.id || index">
<dynamic-content
- v-for="(childData, index) in data.children"
- :key="childData.id || index"
:data="childData"
:widget-name="widgetName"
:level="3"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/status_icon.vue
index 181b8cfad9a..6d17ac98d7f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/status_icon.vue
@@ -48,9 +48,9 @@ export default {
:class="{
[iconClassNameText]: !isLoading,
[`mr-widget-status-icon-level-${level}`]: !isLoading,
- 'gl-mr-3': level === 1,
+ 'gl-w-6 gl-h-6 gl--flex-center': level === 1,
}"
- class="gl-relative gl-w-6 gl-h-6 gl-rounded-full gl--flex-center"
+ class="gl-relative gl-rounded-full gl-mr-3"
>
<gl-loading-icon v-if="isLoading" size="md" inline />
<gl-icon
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 cea7fb8260a..cdf35033021 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,22 +1,18 @@
<script>
-import {
- GlButton,
- GlLink,
- GlTooltipDirective,
- GlLoadingIcon,
- GlSafeHtmlDirective,
-} from '@gitlab/ui';
+import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { normalizeHeaders } from '~/lib/utils/common_utils';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { sprintf, __ } from '~/locale';
import Poll from '~/lib/utils/poll';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
-import ActionButtons from '../action_buttons.vue';
+import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import { EXTENSION_ICONS } from '../../constants';
import { createTelemetryHub } from '../extensions/telemetry';
import ContentRow from './widget_content_row.vue';
import DynamicContent from './dynamic_content.vue';
import StatusIcon from './status_icon.vue';
+import ActionButtons from './action_buttons.vue';
const FETCH_TYPE_COLLAPSED = 'collapsed';
const FETCH_TYPE_EXPANDED = 'expanded';
@@ -31,11 +27,13 @@ export default {
GlLoadingIcon,
ContentRow,
DynamicContent,
+ DynamicScroller,
+ DynamicScrollerItem,
HelpPopover,
},
directives: {
GlTooltip: GlTooltipDirective,
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
},
props: {
/**
@@ -258,6 +256,7 @@ export default {
<div class="gl-display-flex">
<help-popover
v-if="helpPopover"
+ icon="information-o"
:options="helpPopover.options"
:class="{ 'gl-mr-3': actionButtons.length > 0 }"
>
@@ -309,7 +308,7 @@ export default {
<div v-if="isLoadingExpandedContent" class="report-block-container gl-text-center">
<gl-loading-icon size="sm" inline /> {{ loadingText }}
</div>
- <div v-else class="gl-px-5 gl-display-flex">
+ <div v-else class="gl-pl-5 gl-display-flex" :class="{ 'gl-pr-5': $scopedSlots.content }">
<content-row
v-if="contentError"
:level="2"
@@ -322,12 +321,25 @@ export default {
</content-row>
<div v-else class="gl-w-full">
<slot name="content">
- <dynamic-content
- v-for="(data, index) in content"
- :key="data.id || index"
- :data="data"
- :widget-name="widgetName"
- />
+ <dynamic-scroller
+ v-if="content"
+ :items="content"
+ :min-item-size="32"
+ :style="{ maxHeight: '170px' }"
+ data-testid="dynamic-content-scroller"
+ class="gl-pr-5"
+ >
+ <template #default="{ item, index, active }">
+ <dynamic-scroller-item :item="item" :active="active">
+ <dynamic-content
+ :key="item.id || index"
+ :data="item"
+ :widget-name="widgetName"
+ :level="2"
+ />
+ </dynamic-scroller-item>
+ </template>
+ </dynamic-scroller>
</slot>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
index 1fd1e325863..543136dc659 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
@@ -1,10 +1,11 @@
<script>
-import { GlSafeHtmlDirective, GlLink } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
-import ActionButtons from '../action_buttons.vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { EXTENSION_ICONS } from '../../constants';
import { generateText } from '../extensions/utils';
+import ActionButtons from './action_buttons.vue';
import StatusIcon from './status_icon.vue';
export default {
@@ -15,7 +16,7 @@ export default {
ActionButtons,
},
directives: {
- SafeHtml: GlSafeHtmlDirective,
+ SafeHtml,
},
props: {
level: {
@@ -67,6 +68,9 @@ export default {
shouldShowHeaderActions() {
return Boolean(this.helpPopover) || this.actionButtons?.length > 0;
},
+ hasActionButtons() {
+ return this.actionButtons.length > 0;
+ },
},
i18n: {
learnMore: __('Learn more'),
@@ -75,10 +79,15 @@ export default {
</script>
<template>
<div
- class="gl-w-full gl-display-flex mr-widget-content-row gl-align-items-baseline"
+ class="gl-w-full gl-display-flex gl-align-items-baseline"
:class="{ 'gl-border-t gl-py-3 gl-pl-7': level === 2 }"
>
- <status-icon v-if="statusIconName" :level="2" :name="widgetName" :icon-name="statusIconName" />
+ <status-icon
+ v-if="statusIconName && !header"
+ :level="2"
+ :name="widgetName"
+ :icon-name="statusIconName"
+ />
<div class="gl-w-full">
<div class="gl-display-flex">
<slot name="header">
@@ -95,7 +104,12 @@ export default {
v-if="shouldShowHeaderActions"
class="gl-ml-auto gl-display-flex gl-align-items-baseline"
>
- <help-popover v-if="helpPopover" :options="helpPopover.options">
+ <help-popover
+ v-if="helpPopover"
+ :options="helpPopover.options"
+ :class="{ 'gl-mr-3': hasActionButtons }"
+ icon="information-o"
+ >
<template v-if="helpPopover.content">
<p
v-if="helpPopover.content.text"
@@ -112,14 +126,19 @@ export default {
</template>
</help-popover>
<action-buttons
- v-if="actionButtons.length > 0"
+ v-if="hasActionButtons"
:widget="widgetName"
:tertiary-buttons="actionButtons"
- :class="{ 'gl-ml-2': helpPopover }"
/>
</div>
</div>
<div class="gl-display-flex gl-align-items-baseline gl-w-full">
+ <status-icon
+ v-if="statusIconName && header"
+ :level="2"
+ :name="widgetName"
+ :icon-name="statusIconName"
+ />
<slot name="body"></slot>
</div>
</div>