Commit 2977f1d7 authored by Simon Knox's avatar Simon Knox

Merge branch 'fc-refactor-pipeline-card-to-use-flexbox' into 'master'

Refactor child/parent pipeline cards to use flexbox

See merge request gitlab-org/gitlab!80104
parents 1c90db9f 172823da
<script> <script>
import { GlTooltipDirective, GlButton, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui'; import { GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import CiStatus from '~/vue_shared/components/ci_icon.vue'; import CiStatus from '~/vue_shared/components/ci_icon.vue';
...@@ -12,10 +12,10 @@ export default { ...@@ -12,10 +12,10 @@ export default {
}, },
components: { components: {
CiStatus, CiStatus,
GlBadge,
GlButton, GlButton,
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlBadge,
}, },
props: { props: {
columnTitle: { columnTitle: {
...@@ -26,6 +26,10 @@ export default { ...@@ -26,6 +26,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isLoading: {
type: Boolean,
required: true,
},
pipeline: { pipeline: {
type: Object, type: Object,
required: true, required: true,
...@@ -34,33 +38,40 @@ export default { ...@@ -34,33 +38,40 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
isLoading: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
tooltipText() { buttonBorderClass() {
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} - return this.isUpstream ? 'gl-border-r-1!' : 'gl-border-l-1!';
${this.sourceJobInfo}`;
}, },
buttonId() { buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`; return `js-linked-pipeline-${this.pipeline.id}`;
}, },
pipelineStatus() { cardSpacingClass() {
return this.pipeline.status; return this.isDownstream ? 'gl-pr-0' : '';
}, },
projectName() { expandedIcon() {
return this.pipeline.project.name; if (this.isUpstream) {
return this.expanded ? 'angle-right' : 'angle-left';
}
return this.expanded ? 'angle-left' : 'angle-right';
},
childPipeline() {
return this.isDownstream && this.isSameProject;
}, },
downstreamTitle() { downstreamTitle() {
return this.childPipeline ? this.sourceJobName : this.pipeline.project.name; return this.childPipeline ? this.sourceJobName : this.pipeline.project.name;
}, },
parentPipeline() { flexDirection() {
return this.isUpstream && this.isSameProject; return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
}, },
childPipeline() { isDownstream() {
return this.isDownstream && this.isSameProject; return this.type === DOWNSTREAM;
},
isSameProject() {
return !this.pipeline.multiproject;
},
isUpstream() {
return this.type === UPSTREAM;
}, },
label() { label() {
if (this.parentPipeline) { if (this.parentPipeline) {
...@@ -70,17 +81,17 @@ export default { ...@@ -70,17 +81,17 @@ export default {
} }
return __('Multi-project'); return __('Multi-project');
}, },
parentPipeline() {
return this.isUpstream && this.isSameProject;
},
pipelineIsLoading() { pipelineIsLoading() {
return Boolean(this.isLoading || this.pipeline.isLoading); return Boolean(this.isLoading || this.pipeline.isLoading);
}, },
isDownstream() { pipelineStatus() {
return this.type === DOWNSTREAM; return this.pipeline.status;
},
isUpstream() {
return this.type === UPSTREAM;
}, },
isSameProject() { projectName() {
return !this.pipeline.multiproject; return this.pipeline.project.name;
}, },
sourceJobName() { sourceJobName() {
return this.pipeline.sourceJob?.name ?? ''; return this.pipeline.sourceJob?.name ?? '';
...@@ -88,28 +99,23 @@ export default { ...@@ -88,28 +99,23 @@ export default {
sourceJobInfo() { sourceJobInfo() {
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : ''; return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
}, },
expandedIcon() { tooltipText() {
if (this.isUpstream) { return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
return this.expanded ? 'angle-right' : 'angle-left'; ${this.sourceJobInfo}`;
}
return this.expanded ? 'angle-left' : 'angle-right';
},
expandButtonPosition() {
return this.isUpstream ? 'gl-left-0 gl-border-r-1!' : 'gl-right-0 gl-border-l-1!';
}, },
}, },
errorCaptured(err, _vm, info) { errorCaptured(err, _vm, info) {
reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`); reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`);
}, },
methods: { methods: {
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
onClickLinkedPipeline() { onClickLinkedPipeline() {
this.hideTooltips(); this.hideTooltips();
this.$emit('pipelineClicked', this.$refs.linkedPipeline); this.$emit('pipelineClicked', this.$refs.linkedPipeline);
this.$emit('pipelineExpandToggle', this.sourceJobName, !this.expanded); this.$emit('pipelineExpandToggle', this.sourceJobName, !this.expanded);
}, },
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
onDownstreamHovered() { onDownstreamHovered() {
this.$emit('downstreamHovered', this.sourceJobName); this.$emit('downstreamHovered', this.sourceJobName);
}, },
...@@ -124,27 +130,23 @@ export default { ...@@ -124,27 +130,23 @@ export default {
<div <div
ref="linkedPipeline" ref="linkedPipeline"
v-gl-tooltip v-gl-tooltip
class="gl-downstream-pipeline-job-width" class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
:class="flexDirection"
:title="tooltipText" :title="tooltipText"
data-qa-selector="child_pipeline" data-qa-selector="child_pipeline"
@mouseover="onDownstreamHovered" @mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave" @mouseleave="onDownstreamHoverLeave"
> >
<div <div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass">
class="gl-relative gl-bg-white gl-p-3 gl-border-solid gl-border-gray-100 gl-border-1" <div class="gl-display-flex gl-pr-3">
:class="{ 'gl-pl-9': isUpstream }"
>
<div class="gl-display-flex gl-pr-7 gl-pipeline-job-width">
<ci-status <ci-status
v-if="!pipelineIsLoading" v-if="!pipelineIsLoading"
:status="pipelineStatus" :status="pipelineStatus"
:size="24" :size="24"
css-classes="gl-top-0 gl-pr-2" css-classes="gl-top-0 gl-pr-2"
/> />
<div v-else class="gl-pr-2"><gl-loading-icon size="sm" inline /></div> <div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div>
<div <div class="gl-display-flex gl-flex-direction-column gl-downstream-pipeline-job-width">
class="gl-display-flex gl-flex-direction-column gl-pipeline-job-width gl-text-truncate"
>
<span class="gl-text-truncate" data-testid="downstream-title"> <span class="gl-text-truncate" data-testid="downstream-title">
{{ downstreamTitle }} {{ downstreamTitle }}
</span> </span>
...@@ -160,10 +162,12 @@ export default { ...@@ -160,10 +162,12 @@ export default {
{{ label }} {{ label }}
</gl-badge> </gl-badge>
</div> </div>
</div>
<div class="gl-display-flex">
<gl-button <gl-button
:id="buttonId" :id="buttonId"
class="gl-absolute gl-top-0 gl-bottom-0 gl-shadow-none! gl-rounded-0!" class="gl-shadow-none! gl-rounded-0!"
:class="`js-pipeline-expand-${pipeline.id} ${expandButtonPosition}`" :class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`"
:icon="expandedIcon" :icon="expandedIcon"
:aria-label="__('Expand pipeline')" :aria-label="__('Expand pipeline')"
data-testid="expand-pipeline-button" data-testid="expand-pipeline-button"
......
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
} }
.gl-downstream-pipeline-job-width { .gl-downstream-pipeline-job-width {
width: 240px; width: 170px;
} }
.gl-linked-pipeline-padding { .gl-linked-pipeline-padding {
......
...@@ -9,6 +9,23 @@ import mockPipeline from './linked_pipelines_mock_data'; ...@@ -9,6 +9,23 @@ import mockPipeline from './linked_pipelines_mock_data';
describe('Linked pipeline', () => { describe('Linked pipeline', () => {
let wrapper; let wrapper;
const downstreamProps = {
pipeline: {
...mockPipeline,
multiproject: false,
},
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: false,
};
const upstreamProps = {
...downstreamProps,
columnTitle: 'Upstream',
type: UPSTREAM,
};
const findButton = () => wrapper.find(GlButton); const findButton = () => wrapper.find(GlButton);
const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]'); const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]');
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
...@@ -86,91 +103,65 @@ describe('Linked pipeline', () => { ...@@ -86,91 +103,65 @@ describe('Linked pipeline', () => {
}); });
}); });
describe('parent/child', () => { describe('upstream pipelines', () => {
const downstreamProps = { beforeEach(() => {
pipeline: { createWrapper(upstreamProps);
...mockPipeline, });
multiproject: false,
},
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: false,
};
const upstreamProps = { it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
...downstreamProps, expect(findPipelineLabel().exists()).toBe(true);
columnTitle: 'Upstream', });
type: UPSTREAM,
};
it('parent/child label container should exist', () => { it('upstream pipeline should contain the correct link', () => {
expect(findPipelineLink().attributes('href')).toBe(upstreamProps.pipeline.path);
});
it('applies the reverse-row css class to the card', () => {
expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row-reverse');
expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row');
});
});
describe('downstream pipelines', () => {
beforeEach(() => {
createWrapper(downstreamProps); createWrapper(downstreamProps);
});
it('parent/child label container should exist', () => {
expect(findPipelineLabel().exists()).toBe(true); expect(findPipelineLabel().exists()).toBe(true);
}); });
it('should display child label when pipeline project id is the same as triggered pipeline project id', () => { it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
createWrapper(downstreamProps);
expect(findPipelineLabel().exists()).toBe(true); expect(findPipelineLabel().exists()).toBe(true);
}); });
it('should have the name of the trigger job on the card when it is a child pipeline', () => { it('should have the name of the trigger job on the card when it is a child pipeline', () => {
createWrapper(downstreamProps);
expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name); expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name);
}); });
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
createWrapper(upstreamProps);
expect(findPipelineLabel().exists()).toBe(true);
});
it('downstream pipeline should contain the correct link', () => { it('downstream pipeline should contain the correct link', () => {
createWrapper(downstreamProps);
expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path); expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path);
}); });
it('upstream pipeline should contain the correct link', () => { it('applies the flex-row css class to the card', () => {
createWrapper(upstreamProps); expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row');
expect(findPipelineLink().attributes('href')).toBe(upstreamProps.pipeline.path); expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse');
});
}); });
describe('expand button', () => {
it.each` it.each`
presentClass | missingClass pipelineType | anglePosition | borderClass | expanded
${'gl-right-0'} | ${'gl-left-0'} ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-1!'} | ${false}
${'gl-border-l-1!'} | ${'gl-border-r-1!'} ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-1!'} | ${true}
`( ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-1!'} | ${false}
'pipeline expand button should be postioned right when child pipeline', ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-1!'} | ${true}
({ presentClass, missingClass }) => {
createWrapper(downstreamProps);
expect(findExpandButton().classes()).toContain(presentClass);
expect(findExpandButton().classes()).not.toContain(missingClass);
},
);
it.each`
presentClass | missingClass
${'gl-left-0'} | ${'gl-right-0'}
${'gl-border-r-1!'} | ${'gl-border-l-1!'}
`(
'pipeline expand button should be postioned left when parent pipeline',
({ presentClass, missingClass }) => {
createWrapper(upstreamProps);
expect(findExpandButton().classes()).toContain(presentClass);
expect(findExpandButton().classes()).not.toContain(missingClass);
},
);
it.each`
pipelineType | anglePosition | expanded
${downstreamProps} | ${'angle-right'} | ${false}
${downstreamProps} | ${'angle-left'} | ${true}
${upstreamProps} | ${'angle-left'} | ${false}
${upstreamProps} | ${'angle-right'} | ${true}
`( `(
'$pipelineType.columnTitle pipeline button icon should be $anglePosition if expanded state is $expanded', '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $borderClass if expanded state is $expanded',
({ pipelineType, anglePosition, expanded }) => { ({ pipelineType, anglePosition, borderClass, expanded }) => {
createWrapper({ ...pipelineType, expanded }); createWrapper({ ...pipelineType, expanded });
expect(findExpandButton().props('icon')).toBe(anglePosition); expect(findExpandButton().props('icon')).toBe(anglePosition);
expect(findExpandButton().classes()).toContain(borderClass);
}, },
); );
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment