diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components/widget')
6 files changed, 198 insertions, 33 deletions
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> |