diff options
Diffstat (limited to 'app/assets/javascripts/boards/components/board_blocked_icon.vue')
-rw-r--r-- | app/assets/javascripts/boards/components/board_blocked_icon.vue | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/app/assets/javascripts/boards/components/board_blocked_icon.vue b/app/assets/javascripts/boards/components/board_blocked_icon.vue new file mode 100644 index 00000000000..0f92e714752 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_blocked_icon.vue @@ -0,0 +1,192 @@ +<script> +import { GlIcon, GlLink, GlPopover, GlLoadingIcon } from '@gitlab/ui'; +import { blockingIssuablesQueries, issuableTypes } from '~/boards/constants'; +import { IssueType } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { truncate } from '~/lib/utils/text_utility'; +import { __, n__, s__, sprintf } from '~/locale'; + +export default { + i18n: { + issuableType: { + [issuableTypes.issue]: __('issue'), + }, + }, + graphQLIdType: { + [issuableTypes.issue]: IssueType, + }, + referenceFormatter: { + [issuableTypes.issue]: (r) => r.split('/')[1], + }, + defaultDisplayLimit: 3, + textTruncateWidth: 80, + components: { + GlIcon, + GlPopover, + GlLink, + GlLoadingIcon, + }, + blockingIssuablesQueries, + props: { + item: { + type: Object, + required: true, + }, + uniqueId: { + type: String, + required: true, + }, + issuableType: { + type: String, + required: true, + validator(value) { + return [issuableTypes.issue].includes(value); + }, + }, + }, + apollo: { + blockingIssuables: { + skip() { + return this.skip; + }, + query() { + return blockingIssuablesQueries[this.issuableType].query; + }, + variables() { + return { + id: convertToGraphQLId(this.$options.graphQLIdType[this.issuableType], this.item.id), + }; + }, + update(data) { + this.skip = true; + + return data?.issuable?.blockingIssuables?.nodes || []; + }, + error(error) { + const message = sprintf(s__('Boards|Failed to fetch blocking %{issuableType}s'), { + issuableType: this.issuableTypeText, + }); + this.$emit('blocking-issuables-error', { error, message }); + }, + }, + }, + data() { + return { + skip: true, + blockingIssuables: [], + }; + }, + computed: { + displayedIssuables() { + const { defaultDisplayLimit, referenceFormatter } = this.$options; + return this.blockingIssuables.slice(0, defaultDisplayLimit).map((i) => { + return { + ...i, + title: truncate(i.title, this.$options.textTruncateWidth), + reference: referenceFormatter[this.issuableType](i.reference), + }; + }); + }, + loading() { + return this.$apollo.queries.blockingIssuables.loading; + }, + issuableTypeText() { + return this.$options.i18n.issuableType[this.issuableType]; + }, + blockedLabel() { + return sprintf( + n__( + 'Boards|Blocked by %{blockedByCount} %{issuableType}', + 'Boards|Blocked by %{blockedByCount} %{issuableType}s', + this.item.blockedByCount, + ), + { + blockedByCount: this.item.blockedByCount, + issuableType: this.issuableTypeText, + }, + ); + }, + glIconId() { + return `blocked-icon-${this.uniqueId}`; + }, + hasMoreIssuables() { + return this.item.blockedByCount > this.$options.defaultDisplayLimit; + }, + displayedIssuablesCount() { + return this.hasMoreIssuables + ? this.item.blockedByCount - this.$options.defaultDisplayLimit + : this.item.blockedByCount; + }, + moreIssuablesText() { + return sprintf( + n__( + 'Boards|+ %{displayedIssuablesCount} more %{issuableType}', + 'Boards|+ %{displayedIssuablesCount} more %{issuableType}s', + this.displayedIssuablesCount, + ), + { + displayedIssuablesCount: this.displayedIssuablesCount, + issuableType: this.issuableTypeText, + }, + ); + }, + viewAllIssuablesText() { + return sprintf(s__('Boards|View all blocking %{issuableType}s'), { + issuableType: this.issuableTypeText, + }); + }, + loadingMessage() { + return sprintf(s__('Boards|Retrieving blocking %{issuableType}s'), { + issuableType: this.issuableTypeText, + }); + }, + }, + methods: { + handleMouseEnter() { + this.skip = false; + }, + }, +}; +</script> +<template> + <div class="gl-display-inline"> + <gl-icon + :id="glIconId" + ref="icon" + name="issue-block" + class="issue-blocked-icon gl-mr-2 gl-cursor-pointer" + data-testid="issue-blocked-icon" + @mouseenter="handleMouseEnter" + /> + <gl-popover :target="glIconId" placement="top"> + <template #title + ><span data-testid="popover-title">{{ blockedLabel }}</span></template + > + <template v-if="loading"> + <gl-loading-icon /> + <p class="gl-mt-4 gl-mb-0 gl-font-small">{{ loadingMessage }}</p> + </template> + <template v-else> + <ul class="gl-list-style-none gl-p-0"> + <li v-for="issuable in displayedIssuables" :key="issuable.id"> + <gl-link :href="issuable.webUrl" class="gl-text-blue-500! gl-font-sm">{{ + issuable.reference + }}</gl-link> + <p class="gl-mb-3 gl-display-block!" data-testid="issuable-title"> + {{ issuable.title }} + </p> + </li> + </ul> + <div v-if="hasMoreIssuables" class="gl-mt-4"> + <p class="gl-mb-3" data-testid="hidden-blocking-count">{{ moreIssuablesText }}</p> + <gl-link + data-testid="view-all-issues" + :href="`${item.webUrl}#related-issues`" + class="gl-text-blue-500! gl-font-sm" + >{{ viewAllIssuablesText }}</gl-link + > + </div> + </template> + </gl-popover> + </div> +</template> |