gitlab-ce/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue

234 lines
6.4 KiB
Vue

<script>
import {
GlBadge,
GlButton,
GlLink,
GlLoadingIcon,
GlPopover,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import TierBadge from '~/vue_shared/components/tier_badge.vue';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, s__, sprintf } from '~/locale';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import { DOWNSTREAM, UPSTREAM } from './constants';
export default {
i18n: {
popover: {
title: s__('Pipelines|Multi-project pipeline graphs'),
description: s__(
'Pipelines|Gitlab Premium users have access to the multi-project pipeline graph to improve the visualization of these pipelines. %{linkStart}Learn More%{linkEnd}',
),
},
},
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
CiStatus,
GlBadge,
GlButton,
GlLink,
GlLoadingIcon,
GlPopover,
GlSprintf,
TierBadge,
},
inject: ['multiProjectHelpPath'],
props: {
columnTitle: {
type: String,
required: true,
},
expanded: {
type: Boolean,
required: true,
},
isMultiProjectVizAvailable: {
type: Boolean,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
type: {
type: String,
required: true,
},
},
computed: {
buttonBorderClass() {
return this.isUpstream ? 'gl-border-r-1!' : 'gl-border-l-1!';
},
buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`;
},
cardSpacingClass() {
return this.isDownstream ? 'gl-pr-0' : '';
},
expandedIcon() {
if (this.isUpstream) {
return this.expanded ? 'angle-right' : 'angle-left';
}
return this.expanded ? 'angle-left' : 'angle-right';
},
childPipeline() {
return this.isDownstream && this.isSameProject;
},
downstreamTitle() {
return this.childPipeline ? this.sourceJobName : this.pipeline.project.name;
},
flexDirection() {
return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
},
isDownstream() {
return this.type === DOWNSTREAM;
},
isSameProject() {
return !this.pipeline.multiproject;
},
isUpstream() {
return this.type === UPSTREAM;
},
label() {
if (this.parentPipeline) {
return __('Parent');
} else if (this.childPipeline) {
return __('Child');
}
return __('Multi-project');
},
parentPipeline() {
return this.isUpstream && this.isSameProject;
},
pipelineIsLoading() {
return Boolean(this.isLoading || this.pipeline.isLoading);
},
pipelineStatus() {
return this.pipeline.status;
},
popoverContainerId() {
return `popoverContainer-${this.pipeline.id}`;
},
projectName() {
return this.pipeline.project.name;
},
sourceJobName() {
return this.pipeline.sourceJob?.name ?? '';
},
sourceJobInfo() {
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
},
tooltipText() {
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
${this.sourceJobInfo}`;
},
},
errorCaptured(err, _vm, info) {
reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`);
},
methods: {
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
onClickLinkedPipeline() {
this.hideTooltips();
this.$emit('pipelineClicked', this.$refs.linkedPipeline);
this.$emit('pipelineExpandToggle', this.sourceJobName, !this.expanded);
},
onDownstreamHovered() {
this.$emit('downstreamHovered', this.sourceJobName);
},
onDownstreamHoverLeave() {
this.$emit('downstreamHovered', '');
},
},
};
</script>
<template>
<div
class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
:class="flexDirection"
data-qa-selector="child_pipeline"
data-testid="linkedPipeline"
@mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave"
>
<div
v-gl-tooltip
class="gl-w-full gl-bg-white gl-p-3"
:class="cardSpacingClass"
data-testid="linkedPipelineBody"
data-qa-selector="linked_pipeline_body"
:title="tooltipText"
>
<div class="gl-display-flex gl-pr-3">
<ci-status
v-if="!pipelineIsLoading"
:status="pipelineStatus"
:size="24"
css-classes="gl-top-0 gl-pr-2"
/>
<div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div>
<div class="gl-display-flex gl-flex-direction-column gl-downstream-pipeline-job-width">
<span class="gl-text-truncate" data-testid="downstream-title">
{{ downstreamTitle }}
</span>
<div class="gl-text-truncate">
<gl-link class="gl-text-blue-500!" :href="pipeline.path" data-testid="pipelineLink"
>#{{ pipeline.id }}</gl-link
>
</div>
</div>
</div>
<div class="gl-pt-2">
<gl-badge size="sm" variant="info" data-testid="downstream-pipeline-label">
{{ label }}
</gl-badge>
</div>
</div>
<div :id="popoverContainerId" class="gl-display-flex">
<gl-button
:id="buttonId"
class="gl-shadow-none! gl-rounded-0!"
:class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`"
:icon="expandedIcon"
:aria-label="__('Expand pipeline')"
:disabled="!isMultiProjectVizAvailable"
data-testid="expand-pipeline-button"
data-qa-selector="expand_pipeline_button"
@click="onClickLinkedPipeline"
/>
<gl-popover
v-if="!isMultiProjectVizAvailable"
placement="top"
:target="popoverContainerId"
triggers="hover"
>
<template #title>
<b>{{ $options.i18n.popover.title }}</b>
<tier-badge class="gl-mt-3" tier="premium"
/></template>
<p class="gl-my-0">
<gl-sprintf :message="$options.i18n.popover.description">
<template #link="{ content }"
><gl-link :href="multiProjectHelpPath" class="gl-font-sm" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
</gl-popover>
</div>
</div>
</template>