Commit 211f3dbe authored by Miranda Fluharty's avatar Miranda Fluharty Committed by Kushal Pandya

Extend pipeline graph scroll area to full width

Use debounced resize function to set left padding inside graph
Set padding such that the left edge of the pipeline lines up with
the fixed-width container above it in the default scroll position
Also trigger padding recalculation on layout change
parent b0a3500c
......@@ -2,35 +2,43 @@
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue';
import GraphMixin from '../../mixins/graph_component_mixin';
import GraphWidthMixin from '~/pipelines/mixins/graph_width_mixin';
export default {
components: {
StageColumnComponent,
GlLoadingIcon,
},
mixins: [GraphMixin],
mixins: [GraphMixin, GraphWidthMixin],
};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
<div class="pipeline-visualization pipeline-graph pipeline-tab-content">
<div v-if="isLoading" class="m-auto"><gl-loading-icon :size="3" /></div>
<div
:style="{
paddingLeft: `${graphLeftPadding}px`,
paddingRight: `${graphRightPadding}px`,
}"
>
<gl-loading-icon v-if="isLoading" class="m-auto" :size="3" />
<ul v-if="!isLoading" class="stage-column-list">
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
:class="{
'append-right-48': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
<ul v-if="!isLoading" class="stage-column-list">
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
:class="{
'append-right-48': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
</div>
</div>
</div>
</template>
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { LAYOUT_CHANGE_DELAY } from '~/pipelines/constants';
export default {
debouncedResize: null,
sidebarMutationObserver: null,
data() {
return {
graphLeftPadding: 0,
graphRightPadding: 0,
};
},
beforeDestroy() {
window.removeEventListener('resize', this.$options.debouncedResize);
if (this.$options.sidebarMutationObserver) {
this.$options.sidebarMutationObserver.disconnect();
}
},
created() {
this.$options.debouncedResize = debounceByAnimationFrame(this.setGraphPadding);
window.addEventListener('resize', this.$options.debouncedResize);
},
mounted() {
this.setGraphPadding();
this.$options.sidebarMutationObserver = new MutationObserver(this.handleLayoutChange);
this.$options.sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
childList: false,
subtree: false,
});
},
methods: {
setGraphPadding() {
// only add padding to main graph (not inline upstream/downstream graphs)
if (this.type && this.type !== 'main') return;
const container = document.querySelector('.js-pipeline-container');
if (!container) return;
this.graphLeftPadding = container.offsetLeft;
this.graphRightPadding = window.innerWidth - container.offsetLeft - container.offsetWidth;
},
handleLayoutChange() {
// wait until animations finish, then recalculate padding
window.setTimeout(this.setGraphPadding, LAYOUT_CHANGE_DELAY);
},
},
};
......@@ -453,7 +453,7 @@
display: flex;
width: 100%;
background-color: $gray-light;
padding: $gl-padding;
padding: $gl-padding 0;
overflow: auto;
}
......
......@@ -15,7 +15,7 @@
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
.tab-content
#js-tab-pipeline.tab-pane
#js-tab-pipeline.tab-pane.position-absolute.position-left-0.w-100
#js-pipeline-graph-vue
#js-tab-builds.tab-pane
......
---
title: Extend pipeline graph scroll area to full width
merge_request: 14870
author:
type: changed
......@@ -3,6 +3,7 @@ import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import GraphMixin from '~/pipelines/mixins/graph_component_mixin';
import GraphWidthMixin from '~/pipelines/mixins/graph_width_mixin';
import LinkedPipelinesColumn from 'ee/pipelines/components/graph/linked_pipelines_column.vue';
import GraphEEMixin from 'ee/pipelines/mixins/graph_pipeline_bundle_mixin';
......@@ -13,7 +14,7 @@ export default {
GlLoadingIcon,
LinkedPipelinesColumn,
},
mixins: [GraphMixin, GraphEEMixin],
mixins: [GraphMixin, GraphWidthMixin, GraphEEMixin],
props: {
isLoading: {
type: Boolean,
......@@ -102,82 +103,89 @@ export default {
class="pipeline-visualization pipeline-graph"
:class="{ 'pipeline-tab-content': !isLinkedPipeline }"
>
<div v-if="isLoading" class="m-auto"><gl-loading-icon :size="3" /></div>
<div
:style="{
paddingLeft: `${graphLeftPadding}px`,
paddingRight: `${graphRightPadding}px`,
}"
>
<gl-loading-icon v-if="isLoading" class="m-auto" :size="3" />
<pipeline-graph
v-if="type !== $options.downstream && expandedTriggeredBy"
type="upstream"
class="d-inline-block upstream-pipeline"
:class="`js-upstream-pipeline-${expandedTriggeredBy.id}`"
:is-loading="false"
:pipeline="expandedTriggeredBy"
:is-linked-pipeline="true"
:mediator="mediator"
@onClickTriggeredBy="
(parentPipeline, pipeline) => clickTriggeredByPipeline(parentPipeline, pipeline)
"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
<pipeline-graph
v-if="type !== $options.downstream && expandedTriggeredBy"
type="upstream"
class="d-inline-block upstream-pipeline"
:class="`js-upstream-pipeline-${expandedTriggeredBy.id}`"
:is-loading="false"
:pipeline="expandedTriggeredBy"
:is-linked-pipeline="true"
:mediator="mediator"
@onClickTriggeredBy="
(parentPipeline, pipeline) => clickTriggeredByPipeline(parentPipeline, pipeline)
"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
<linked-pipelines-column
v-if="hasTriggeredBy"
:linked-pipelines="triggeredByPipelines"
:column-title="__('Upstream')"
graph-position="left"
@linkedPipelineClick="
linkedPipeline => $emit('onClickTriggeredBy', pipeline, linkedPipeline)
"
/>
<linked-pipelines-column
v-if="hasTriggeredBy"
:linked-pipelines="triggeredByPipelines"
:column-title="__('Upstream')"
graph-position="left"
@linkedPipelineClick="
linkedPipeline => $emit('onClickTriggeredBy', pipeline, linkedPipeline)
"
/>
<ul
v-if="!isLoading"
:class="{
'inline js-has-linked-pipelines': hasTriggered || hasTriggeredBy,
}"
class="stage-column-list align-top"
>
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
<ul
v-if="!isLoading"
:class="{
'has-upstream prepend-left-64': index === 0 && hasTriggeredBy,
'has-downstream': index === graph.length - 1 && hasTriggered,
'has-only-one-job': hasOnlyOneJob(stage),
'append-right-46': shouldAddRightMargin(index),
'inline js-has-linked-pipelines': hasTriggered || hasTriggeredBy,
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
class="stage-column-list align-top"
>
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
:class="{
'has-upstream prepend-left-64': index === 0 && hasTriggeredBy,
'has-downstream': index === graph.length - 1 && hasTriggered,
'has-only-one-job': hasOnlyOneJob(stage),
'append-right-46': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
<linked-pipelines-column
v-if="hasTriggered"
:linked-pipelines="triggeredPipelines"
:column-title="__('Downstream')"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
/>
<linked-pipelines-column
v-if="hasTriggered"
:linked-pipelines="triggeredPipelines"
:column-title="__('Downstream')"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
/>
<pipeline-graph
v-if="type !== $options.upstream && expandedTriggered"
type="downstream"
class="d-inline-block"
:class="`js-downstream-pipeline-${expandedTriggered.id}`"
:is-loading="false"
:pipeline="expandedTriggered"
:is-linked-pipeline="true"
:style="{ 'margin-top': marginTop }"
:mediator="mediator"
@onClickTriggered="
(parentPipeline, pipeline) => clickTriggeredPipeline(parentPipeline, pipeline)
"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
<pipeline-graph
v-if="type !== $options.upstream && expandedTriggered"
type="downstream"
class="d-inline-block"
:class="`js-downstream-pipeline-${expandedTriggered.id}`"
:is-loading="false"
:pipeline="expandedTriggered"
:is-linked-pipeline="true"
:style="{ 'margin-top': marginTop }"
:mediator="mediator"
@onClickTriggered="
(parentPipeline, pipeline) => clickTriggeredPipeline(parentPipeline, pipeline)
"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
</div>
</div>
</div>
</template>
......@@ -14,6 +14,12 @@ describe('graph component', () => {
let component;
beforeEach(() => {
setFixtures(`
<div class="layout-page"></div>
`);
});
afterEach(() => {
component.$destroy();
});
......@@ -130,7 +136,7 @@ describe('graph component', () => {
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered_by[0].isExpanded = true;
......@@ -140,7 +146,12 @@ describe('graph component', () => {
mediator,
});
expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull();
Vue.nextTick()
.then(() => {
expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
......@@ -161,7 +172,7 @@ describe('graph component', () => {
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered[0].isExpanded = true;
......@@ -171,7 +182,14 @@ describe('graph component', () => {
mediator,
});
expect(component.$el.querySelector('.js-downstream-pipeline-34993051')).not.toBeNull();
Vue.nextTick()
.then(() => {
expect(
component.$el.querySelector('.js-downstream-pipeline-34993051'),
).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
......
......@@ -7,6 +7,12 @@ describe('graph component', () => {
const GraphComponent = Vue.extend(graphComponent);
let component;
beforeEach(() => {
setFixtures(`
<div class="layout-page"></div>
`);
});
afterEach(() => {
component.$destroy();
});
......
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