Commit e7b2d546 authored by Payton Burdette's avatar Payton Burdette Committed by Kushal Pandya

Add child and parent labels

Adds child/parent labels to either
a parent pipeline or a child pipeline
this is dependent on the data returned
from the triggered or triggered_by array
responses from the controller.

Generate changelog entry for new feat

Generate locale/gitlab.pot file

Refactor child/parent labels

BE proposed new data returned
from the endpoint. I have to adjust FE
to determine if a pipeline project id
is the same as the linked pipelines project
id. That will determine if the current pipeline
is a child or a parent pipeline.

Use scss variable instead of hard coded

Use scss variable with a padding value
of 4px instead of hard coding a padding
value on a scss class.

Fix eslint issues

Fixed eslint issued for passing props
without a dash. And had to disable next
line of the mock data file for testing. I
need the duplicate key.

Refactor to determine label type

Ran into issue where I was expecting
a key value that will not exist had to
come up with a workaround. String matches
should be refactored, once BE supports the data
return we need.

Remove scss moving to another MR

This MR isn't ready to be merged,
so I'm moving this fix to another MR
to get it user facing ASAP.

Add tooltips for parent/child pipelines

Added tooltips and tooltip text for
a parent or child pipeline. Also when
hovered over the label it hides tooltip
for the pipeline so two aren't showing at
the same time.

Add dynamic class for child pipeline

Needed more margin bottom for a child pipeline
so the downstream pipelines would not overlap.
Also fixed what tooltip text is returned for
a label.

Remove margin bottom on last child

Make sure if a child pipeline is the
last element in the column that the
new margin bottom is removed.

Add back fix for removing extra lines

Should of let a merge conflict do this
when my Other MR fixed a small UI bug.

Refactor how margin top is calc

For downstream pipelines, the current
margin top calc will not work since the
nodes now have extra info. Needed to adjust
that and move some scss to new file.

Refactor how margin top is calculated

Since introducing new nodes of different
sizes the old way of calculating margin
top for downstream nodes will no longer work.
I've introduced a new way to calculate how
margin top is calculated.

Refactor linked pipeline tests

Had duplicate project ids in the mock
data, fixed that. Also had to change the
way we checked if an event was emitted due
to now passing data with the event.

Fix eslint and static analysis errors

Needed to regenerate gitlab.pot and
order attributes correctly in linked
pipeline component.
parent 035c7ad7
......@@ -43,7 +43,7 @@ export default {
downstream: 'downstream',
data() {
return {
triggeredTopIndex: 1,
downstreamMarginTop: null,
};
},
computed: {
......@@ -77,26 +77,34 @@ export default {
expandedTriggered() {
return this.pipeline.triggered && this.pipeline.triggered.find(el => el.isExpanded);
},
/**
* Calculates the margin top of the clicked downstream pipeline by
* adding the height of each linked pipeline and the margin
*/
marginTop() {
return `${this.triggeredTopIndex * 52}px`;
},
pipelineTypeUpstream() {
return this.type !== this.$options.downstream && this.expandedTriggeredBy;
},
pipelineTypeDownstream() {
return this.type !== this.$options.upstream && this.expandedTriggered;
},
pipelineProjectId() {
return this.pipeline.project.id;
},
},
methods: {
handleClickedDownstream(pipeline, clickedIndex) {
this.triggeredTopIndex = clickedIndex;
handleClickedDownstream(pipeline, clickedIndex, downstreamNode) {
/**
* Calculates the margin top of the clicked downstream pipeline by
* subtracting the clicked downstream pipelines offsetTop by it's parent's
* offsetTop and then subtracting either 15 (if child) or 30 (if not a child)
* due to the height of node and stage name margin bottom.
*/
this.downstreamMarginTop = this.calculateMarginTop(
downstreamNode,
downstreamNode.classList.contains('child-pipeline') ? 15 : 30,
);
this.$emit('onClickTriggered', this.pipeline, pipeline);
},
calculateMarginTop(downstreamNode, pixelDiff) {
return `${downstreamNode.offsetTop - downstreamNode.offsetParent.offsetTop - pixelDiff}px`;
},
hasOnlyOneJob(stage) {
return stage.groups.length === 1;
},
......@@ -139,6 +147,7 @@ export default {
v-if="hasTriggeredBy"
:linked-pipelines="triggeredByPipelines"
:column-title="__('Upstream')"
:project-id="pipelineProjectId"
graph-position="left"
@linkedPipelineClick="
linkedPipeline => $emit('onClickTriggeredBy', pipeline, linkedPipeline)
......@@ -174,6 +183,7 @@ export default {
v-if="hasTriggered"
:linked-pipelines="triggeredPipelines"
:column-title="__('Downstream')"
:project-id="pipelineProjectId"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
/>
......@@ -186,7 +196,7 @@ export default {
:is-loading="false"
:pipeline="expandedTriggered"
:is-linked-pipeline="true"
:style="{ 'margin-top': marginTop }"
:style="{ 'margin-top': downstreamMarginTop }"
:mediator="mediator"
@onClickTriggered="
(parentPipeline, pipeline) => clickTriggeredPipeline(parentPipeline, pipeline)
......
<script>
import { GlLoadingIcon, GlTooltipDirective, GlButton } from '@gitlab/ui';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import { __ } from '~/locale';
export default {
directives: {
......@@ -16,6 +17,14 @@ export default {
type: Object,
required: true,
},
projectId: {
type: Number,
required: true,
},
columnTitle: {
type: String,
required: true,
},
},
computed: {
tooltipText() {
......@@ -30,18 +39,45 @@ export default {
projectName() {
return this.pipeline.project.name;
},
parentPipeline() {
// Refactor string match when BE returns Upstream/Downstream indicators
return this.projectId === this.pipeline.project.id && this.columnTitle === __('Upstream');
},
childPipeline() {
// Refactor string match when BE returns Upstream/Downstream indicators
return this.projectId === this.pipeline.project.id && this.columnTitle === __('Downstream');
},
label() {
return this.parentPipeline ? __('Parent') : __('Child');
},
childTooltipText() {
return __('This pipeline was triggered by a parent pipeline');
},
parentTooltipText() {
return __('This pipeline triggered a child pipeline');
},
labelToolTipText() {
return this.label === __('Parent') ? this.parentTooltipText : this.childTooltipText;
},
},
methods: {
onClickLinkedPipeline() {
this.$root.$emit('bv::hide::tooltip', this.buttonId);
this.$emit('pipelineClicked');
this.$emit('pipelineClicked', this.$refs.linkedPipeline);
},
hideTooltips() {
this.$root.$emit('bv::hide::tooltip');
},
},
};
</script>
<template>
<li class="linked-pipeline build">
<li
ref="linkedPipeline"
class="linked-pipeline build"
:class="{ 'child-pipeline': childPipeline }"
>
<gl-button
:id="buttonId"
v-gl-tooltip
......@@ -59,6 +95,15 @@ export default {
class="js-linked-pipeline-status"
/>
<span class="str-truncated align-bottom"> {{ projectName }} &#8226; #{{ pipeline.id }} </span>
<div v-if="parentPipeline || childPipeline" class="parent-child-label-container">
<span
v-gl-tooltip.bottom
:title="labelToolTipText"
class="badge badge-primary"
@mouseover="hideTooltips"
>{{ label }}</span
>
</div>
</gl-button>
</li>
</template>
......@@ -19,6 +19,10 @@ export default {
type: String,
required: true,
},
projectId: {
type: Number,
required: true,
},
},
computed: {
columnClass() {
......@@ -28,10 +32,16 @@ export default {
};
return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
},
// Refactor string match when BE returns Upstream/Downstream indicators
isUpstream() {
return this.columnTitle === __('Upstream');
},
},
methods: {
onPipelineClick(downstreamNode, pipeline, index) {
this.$emit('linkedPipelineClick', pipeline, index, downstreamNode);
},
},
};
</script>
......@@ -48,7 +58,9 @@ export default {
'left-connector': pipeline.isExpanded && graphPosition === 'left',
}"
:pipeline="pipeline"
@pipelineClicked="$emit('linkedPipelineClick', pipeline, index)"
:column-title="columnTitle"
:project-id="projectId"
@pipelineClicked="onPipelineClick($event, pipeline, index)"
/>
</ul>
</div>
......
......@@ -1093,3 +1093,7 @@ button.mini-pipeline-graph-dropdown-toggle {
.progress-bar.bg-primary {
background-color: $blue-500 !important;
}
.parent-child-label-container {
padding-top: $gl-padding-4;
}
---
title: Add child and parent labels to pipelines
merge_request: 21332
author:
type: added
......@@ -163,6 +163,10 @@
}
}
&.child-pipeline {
height: 68px;
}
.linked-pipeline-content {
@include build-content(0);
text-align: inherit;
......
......@@ -3344,6 +3344,9 @@ msgstr ""
msgid "Cherry-pick this merge request"
msgstr ""
msgid "Child"
msgstr ""
msgid "Child epic does not exist."
msgstr ""
......@@ -12879,6 +12882,9 @@ msgstr ""
msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
msgstr ""
msgid "Parent"
msgstr ""
msgid "Parent epic doesn't exist."
msgstr ""
......@@ -18878,6 +18884,12 @@ msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
msgstr ""
msgid "This pipeline triggered a child pipeline"
msgstr ""
msgid "This pipeline was triggered by a parent pipeline"
msgstr ""
msgid "This project"
msgstr ""
......
......@@ -8,6 +8,13 @@ const mockPipeline = mockData.triggered[0];
describe('Linked pipeline', () => {
let wrapper;
const createWrapper = propsData => {
wrapper = mount(LinkedPipelineComponent, {
attachToDocument: true,
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
......@@ -15,13 +22,12 @@ describe('Linked pipeline', () => {
describe('rendered output', () => {
const props = {
pipeline: mockPipeline,
projectId: 20,
columnTitle: 'Downstream',
};
beforeEach(() => {
wrapper = mount(LinkedPipelineComponent, {
attachToDocument: true,
propsData: props,
});
createWrapper(props);
});
it('should render a list item as the containing element', () => {
......@@ -73,18 +79,50 @@ describe('Linked pipeline', () => {
it('does not render the loading icon when isLoading is false', () => {
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(false);
});
it('should not display child label when pipeline project id is not the same as triggered pipeline project id', () => {
const labelContainer = wrapper.find('.parent-child-label-container');
expect(labelContainer.exists()).toBe(false);
});
});
describe('parent/child', () => {
const downstreamProps = {
pipeline: mockPipeline,
projectId: 19,
columnTitle: 'Downstream',
};
const upstreamProps = {
...downstreamProps,
columnTitle: 'Upstream',
};
it('parent/child label container should exist', () => {
createWrapper(downstreamProps);
expect(wrapper.find('.parent-child-label-container').exists()).toBe(true);
});
it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
createWrapper(downstreamProps);
expect(wrapper.find('.parent-child-label-container').text()).toContain('Child');
});
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
createWrapper(upstreamProps);
expect(wrapper.find('.parent-child-label-container').text()).toContain('Parent');
});
});
describe('when isLoading is true', () => {
const props = {
pipeline: { ...mockPipeline, isLoading: true },
projectId: 19,
columnTitle: 'Downstream',
};
beforeEach(() => {
wrapper = mount(LinkedPipelineComponent, {
attachToDocument: true,
propsData: props,
});
createWrapper(props);
});
it('renders a loading icon', () => {
......@@ -95,20 +133,19 @@ describe('Linked pipeline', () => {
describe('on click', () => {
const props = {
pipeline: mockPipeline,
projectId: 19,
columnTitle: 'Downstream',
};
beforeEach(() => {
wrapper = mount(LinkedPipelineComponent, {
attachToDocument: true,
propsData: props,
});
createWrapper(props);
});
it('emits `pipelineClicked` event', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper.find('button').trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineClicked');
expect(wrapper.emitted().pipelineClicked).toBeTruthy();
});
it('should emit `bv::hide::tooltip` to close the tooltip', () => {
......
export default {
project: {
id: 19,
},
triggered_by: {
id: 129,
active: true,
......@@ -63,6 +66,7 @@ export default {
path: '/gitlab-org/gitlab-foss/pipelines/132',
project: {
name: 'GitLabCE',
id: 19,
},
details: {
status: {
......
......@@ -190,6 +190,7 @@ describe('graph component', () => {
describe('on click', () => {
it('should emit `onClickTriggered`', () => {
spyOn(component, '$emit');
spyOn(component, 'calculateMarginTop').and.callFake(() => '16px');
component.$el.querySelector('#js-linked-pipeline-34993051').click();
......
......@@ -9,6 +9,7 @@ describe('Linked Pipelines Column', () => {
columnTitle: 'Upstream',
linkedPipelines: mockData.triggered,
graphPosition: 'right',
projectId: 19,
};
let vm;
......
......@@ -341,6 +341,9 @@
"commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46",
"commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46"
},
"project": {
"id": 1794617
},
"triggered_by": {
"id": 12,
"user": {
......
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