Commit 704baf56 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Sarah Groff Hennigh-Palermo

Update the Pipeline editor viz to look like Pipeline Graph

parent 14f00cbb
......@@ -101,9 +101,6 @@ export default {
showJobLinks() {
return !this.isStageView && this.showLinks;
},
shouldShowStageName() {
return !this.isStageView;
},
// The show downstream check prevents showing redundant linked columns
showDownstreamPipelines() {
return (
......@@ -202,7 +199,7 @@ export default {
:groups="column.groups"
:action="column.status.action"
:highlighted-jobs="highlightedJobs"
:show-stage-name="shouldShowStageName"
:is-stage-view="isStageView"
:job-hovered="hoveredJobName"
:source-job-hovered="hoveredSourceJobName"
:pipeline-expanded="pipelineExpanded"
......
......@@ -40,6 +40,11 @@ export default {
required: false,
default: () => [],
},
isStageView: {
type: Boolean,
required: false,
default: false,
},
jobHovered: {
type: String,
required: false,
......@@ -50,11 +55,6 @@ export default {
required: false,
default: () => ({}),
},
showStageName: {
type: Boolean,
required: false,
default: false,
},
sourceJobHovered: {
type: String,
required: false,
......@@ -73,6 +73,12 @@ export default {
'gl-pl-3',
],
computed: {
canUpdatePipeline() {
return this.userPermissions.updatePipeline;
},
columnSpacingClass() {
return this.isStageView ? 'gl-px-6' : 'gl-px-9';
},
/*
currentGroups and filteredGroups are part of
a test to hunt down a bug
......@@ -94,8 +100,8 @@ export default {
hasAction() {
return !isEmpty(this.action);
},
canUpdatePipeline() {
return this.userPermissions.updatePipeline;
showStageName() {
return !this.isStageView;
},
},
errorCaptured(err, _vm, info) {
......@@ -130,7 +136,7 @@ export default {
};
</script>
<template>
<main-graph-wrapper class="gl-px-6" data-testid="stage-column">
<main-graph-wrapper :class="columnSpacingClass" data-testid="stage-column">
<template #stages>
<div
data-testid="stage-column-title"
......
......@@ -75,11 +75,11 @@ export const generateLinksData = ({ links }, containerID, modifier = '') => {
// until we can safely draw the bezier to look nice.
// The adjustment number here is a magic number to make things
// look nice and should change if the padding changes. This goes well
// with gl-px-6. gl-px-8 is more like 100.
const straightLineDestinationX = targetNodeX - 60;
// with gl-px-9 which we translate with 100px here.
const straightLineDestinationX = targetNodeX - 100;
const controlPointX = straightLineDestinationX + (targetNodeX - straightLineDestinationX) / 2;
if (straightLineDestinationX > 0) {
if (straightLineDestinationX > firstPointCoordinateX) {
path.lineTo(straightLineDestinationX, sourceNodeY);
}
......
......@@ -14,7 +14,7 @@ export default {
type: Number,
required: true,
},
isHighlighted: {
isHovered: {
type: Boolean,
required: false,
default: false,
......@@ -42,7 +42,7 @@ export default {
jobPillClasses() {
return [
{ 'gl-opacity-3': this.isFadedOut },
this.isHighlighted ? 'gl-shadow-blue-200-x0-y0-b4-s2' : 'gl-inset-border-2-green-400',
{ 'gl-bg-gray-50 gl-inset-border-1-gray-200': this.isHovered },
];
},
},
......@@ -57,15 +57,17 @@ export default {
};
</script>
<template>
<tooltip-on-truncate :title="jobName" truncate-target="child" placement="top">
<div
:id="id"
class="gl-w-15 gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease"
:class="jobPillClasses"
@mouseover="onMouseEnter"
@mouseleave="onMouseLeave"
>
{{ jobName }}
</div>
</tooltip-on-truncate>
<div class="gl-w-full">
<tooltip-on-truncate :title="jobName" truncate-target="child" placement="top">
<div
:id="id"
class="gl-bg-white gl-inset-border-1-gray-100 gl-text-center gl-text-truncate gl-rounded-6 gl-mb-3 gl-px-5 gl-py-3 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease"
:class="jobPillClasses"
@mouseover="onMouseEnter"
@mouseleave="onMouseLeave"
>
{{ jobName }}
</div>
</tooltip-on-truncate>
</div>
</template>
......@@ -4,14 +4,14 @@ import { __ } from '~/locale';
import { DRAW_FAILURE, DEFAULT } from '../../constants';
import LinksLayer from '../graph_shared/links_layer.vue';
import JobPill from './job_pill.vue';
import StagePill from './stage_pill.vue';
import StageName from './stage_name.vue';
export default {
components: {
GlAlert,
JobPill,
LinksLayer,
StagePill,
StageName,
},
CONTAINER_REF: 'PIPELINE_GRAPH_CONTAINER_REF',
BASE_CONTAINER_ID: 'pipeline-graph-container',
......@@ -21,6 +21,11 @@ export default {
[DRAW_FAILURE]: __('Could not draw the lines for job relationships'),
[DEFAULT]: __('An unknown error occurred.'),
},
// The combination of gl-w-full gl-min-w-full and gl-max-w-15 is necessary.
// The max width and the width make sure the ellipsis to work and the min width
// is for when there is less text than the stage column width (which the width 100% does not fix)
jobWrapperClasses:
'gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8 gl-min-w-full gl-max-w-15',
props: {
pipelineData: {
required: true,
......@@ -85,23 +90,8 @@ export default {
height: this.$refs[this.$options.CONTAINER_REF].scrollHeight,
};
},
getStageBackgroundClasses(index) {
const { length } = this.pipelineStages;
// It's possible for a graph to have only one stage, in which
// case we concatenate both the left and right rounding classes
if (length === 1) {
return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6 gl-rounded-bottom-right-6 gl-rounded-top-right-6';
}
if (index === 0) {
return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6';
}
if (index === length - 1) {
return 'gl-rounded-bottom-right-6 gl-rounded-top-right-6';
}
return '';
isFadedOut(jobName) {
return this.highlightedJobs.length > 1 && !this.isJobHighlighted(jobName);
},
isJobHighlighted(jobName) {
return this.highlightedJobs.includes(jobName);
......@@ -137,7 +127,12 @@ export default {
>
{{ failure.text }}
</gl-alert>
<div :id="containerId" :ref="$options.CONTAINER_REF" data-testid="graph-container">
<div
:id="containerId"
:ref="$options.CONTAINER_REF"
class="gl-bg-gray-10 gl-overflow-auto"
data-testid="graph-container"
>
<links-layer
:pipeline-data="pipelineStages"
:pipeline-id="$options.PIPELINE_ID"
......@@ -152,23 +147,17 @@ export default {
:key="`${stage.name}-${index}`"
class="gl-flex-direction-column"
>
<div
class="gl-display-flex gl-align-items-center gl-bg-white gl-w-full gl-px-8 gl-py-4 gl-mb-5"
:class="getStageBackgroundClasses(index)"
data-testid="stage-background"
>
<stage-pill :stage-name="stage.name" :is-empty="stage.groups.length === 0" />
<div class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5">
<stage-name :stage-name="stage.name" />
</div>
<div
class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8"
>
<div :class="$options.jobWrapperClasses">
<job-pill
v-for="group in stage.groups"
:key="group.name"
:job-name="group.name"
:pipeline-id="$options.PIPELINE_ID"
:is-highlighted="hasHighlightedJob && isJobHighlighted(group.name)"
:is-faded-out="hasHighlightedJob && !isJobHighlighted(group.name)"
:is-hovered="highlightedJob === group.name"
:is-faded-out="isFadedOut(group.name)"
@on-mouse-enter="setHoveredJob"
@on-mouse-leave="removeHoveredJob"
/>
......
<script>
import { capitalize, escape } from 'lodash';
import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
export default {
......@@ -10,26 +11,18 @@ export default {
type: String,
required: true,
},
isEmpty: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
emptyClass() {
return this.isEmpty ? 'gl-bg-gray-200' : 'gl-bg-gray-600';
formattedTitle() {
return capitalize(escape(this.stageName));
},
},
};
</script>
<template>
<tooltip-on-truncate :title="stageName" truncate-target="child" placement="top">
<div
class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill gl-w-20"
:class="emptyClass"
>
{{ stageName }}
<div class="gl-py-2 gl-text-truncate gl-font-weight-bold gl-w-20">
{{ formattedTitle }}
</div>
</tooltip-on-truncate>
</template>
......@@ -56,7 +56,6 @@ describe('stage column component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when mounted', () => {
......
......@@ -2,29 +2,29 @@
exports[`Links Inner component with a large number of needs matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118L52,118C82,118,82,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M222,138L62,138C92,138,92,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M212,128L72,128C102,128,102,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M232,148L82,148C112,148,112,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118C52,118,52,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118C62,118,62,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M222,138C72,138,72,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M212,128C82,128,82,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M232,148C92,148,92,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
exports[`Links Inner component with a parallel need matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
<path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M192,108C32,108,32,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
exports[`Links Inner component with one need matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118C52,118,52,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
exports[`Links Inner component with same stage needs matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
<path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118L32,118C62,118,62,128,92,128\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M192,108C32,108,32,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118C42,118,42,128,92,128\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
......@@ -6,7 +6,7 @@ import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue';
import StageName from '~/pipelines/components/pipeline_graph/stage_name.vue';
import { pipelineData, singleStageData } from './mock_data';
describe('pipeline graph component', () => {
......@@ -35,11 +35,9 @@ describe('pipeline graph component', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findAllJobPills = () => wrapper.findAll(JobPill);
const findAllStageBackgroundElements = () => wrapper.findAll('[data-testid="stage-background"]');
const findAllStagePills = () => wrapper.findAllComponents(StagePill);
const findAllStageNames = () => wrapper.findAllComponents(StageName);
const findLinksLayer = () => wrapper.findComponent(LinksLayer);
const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
const findStageBackgroundElementAt = (index) => findAllStageBackgroundElements().at(index);
afterEach(() => {
wrapper.destroy();
......@@ -67,10 +65,10 @@ describe('pipeline graph component', () => {
wrapper = createComponent({ pipelineData: singleStageData });
});
it('renders the right number of stage pills', () => {
it('renders the right number of stage titles', () => {
const expectedStagesLength = singleStageData.stages.length;
expect(findAllStagePills()).toHaveLength(expectedStagesLength);
expect(findAllStageNames()).toHaveLength(expectedStagesLength);
});
it('renders the right number of job pills', () => {
......@@ -81,20 +79,6 @@ describe('pipeline graph component', () => {
expect(findAllJobPills()).toHaveLength(expectedJobsLength);
});
describe('rounds corner', () => {
it.each`
cssClass | expectedState
${'gl-rounded-bottom-left-6'} | ${true}
${'gl-rounded-top-left-6'} | ${true}
${'gl-rounded-top-right-6'} | ${true}
${'gl-rounded-bottom-right-6'} | ${true}
`('$cssClass should be $expectedState on the only element', ({ cssClass, expectedState }) => {
const classes = findStageBackgroundElementAt(0).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
});
});
});
describe('with multiple stages and jobs', () => {
......@@ -102,10 +86,10 @@ describe('pipeline graph component', () => {
wrapper = createComponent();
});
it('renders the right number of stage pills', () => {
it('renders the right number of stage titles', () => {
const expectedStagesLength = pipelineData.stages.length;
expect(findAllStagePills()).toHaveLength(expectedStagesLength);
expect(findAllStageNames()).toHaveLength(expectedStagesLength);
});
it('renders the right number of job pills', () => {
......@@ -116,34 +100,5 @@ describe('pipeline graph component', () => {
expect(findAllJobPills()).toHaveLength(expectedJobsLength);
});
describe('rounds corner', () => {
it.each`
cssClass | expectedState
${'gl-rounded-bottom-left-6'} | ${true}
${'gl-rounded-top-left-6'} | ${true}
${'gl-rounded-top-right-6'} | ${false}
${'gl-rounded-bottom-right-6'} | ${false}
`(
'$cssClass should be $expectedState on the first element',
({ cssClass, expectedState }) => {
const classes = findStageBackgroundElementAt(0).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
},
);
it.each`
cssClass | expectedState
${'gl-rounded-bottom-left-6'} | ${false}
${'gl-rounded-top-left-6'} | ${false}
${'gl-rounded-top-right-6'} | ${true}
${'gl-rounded-bottom-right-6'} | ${true}
`('$cssClass should be $expectedState on the last element', ({ cssClass, expectedState }) => {
const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
});
});
});
});
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