Commit 92d18d7a authored by Payton Burdette's avatar Payton Burdette Committed by Filipa Lacerda

Port over more pipeline files to ce

This commit ports over some files I missed
in my first commit because I forgot to stage
them in the first commit.
parent 37a7dd8e
<script> <script>
import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue'; import StageColumnComponent from './stage_column_component.vue';
import GraphMixin from '../../mixins/graph_component_mixin'; import GraphMixin from '../../mixins/graph_component_mixin';
import GraphWidthMixin from '~/pipelines/mixins/graph_width_mixin'; import GraphWidthMixin from '../../mixins/graph_width_mixin';
import LinkedPipelinesColumn from './linked_pipelines_column.vue';
import GraphBundleMixin from '../../mixins/graph_pipeline_bundle_mixin';
export default { export default {
name: 'PipelineGraph',
components: { components: {
StageColumnComponent, StageColumnComponent,
GlLoadingIcon, GlLoadingIcon,
LinkedPipelinesColumn,
},
mixins: [GraphMixin, GraphWidthMixin, GraphBundleMixin],
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
isLinkedPipeline: {
type: Boolean,
required: false,
default: false,
},
mediator: {
type: Object,
required: true,
},
type: {
type: String,
required: false,
default: 'main',
},
},
upstream: 'upstream',
downstream: 'downstream',
data() {
return {
triggeredTopIndex: 1,
};
},
computed: {
hasTriggeredBy() {
return (
this.type !== this.$options.downstream &&
this.triggeredByPipelines &&
this.pipeline.triggered_by !== null
);
},
triggeredByPipelines() {
return this.pipeline.triggered_by;
},
hasTriggered() {
return (
this.type !== this.$options.upstream &&
this.triggeredPipelines &&
this.pipeline.triggered.length > 0
);
},
triggeredPipelines() {
return this.pipeline.triggered;
},
expandedTriggeredBy() {
return (
this.pipeline.triggered_by &&
_.isArray(this.pipeline.triggered_by) &&
this.pipeline.triggered_by.find(el => el.isExpanded)
);
},
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;
},
},
methods: {
handleClickedDownstream(pipeline, clickedIndex) {
this.triggeredTopIndex = clickedIndex;
this.$emit('onClickTriggered', this.pipeline, pipeline);
},
hasOnlyOneJob(stage) {
return stage.groups.length === 1;
},
hasDownstream(index, length) {
return index === length - 1 && this.hasTriggered;
},
hasUpstream(index) {
return index === 0 && this.hasTriggeredBy;
},
}, },
mixins: [GraphMixin, GraphWidthMixin],
}; };
</script> </script>
<template> <template>
<div class="build-content middle-block js-pipeline-graph"> <div class="build-content middle-block js-pipeline-graph">
<div class="pipeline-visualization pipeline-graph pipeline-tab-content"> <div
class="pipeline-visualization pipeline-graph"
:class="{ 'pipeline-tab-content': !isLinkedPipeline }"
>
<div <div
:style="{ :style="{
paddingLeft: `${graphLeftPadding}px`, paddingLeft: `${graphLeftPadding}px`,
...@@ -23,21 +123,80 @@ export default { ...@@ -23,21 +123,80 @@ export default {
> >
<gl-loading-icon v-if="isLoading" class="m-auto" :size="3" /> <gl-loading-icon v-if="isLoading" class="m-auto" :size="3" />
<ul v-if="!isLoading" class="stage-column-list"> <pipeline-graph
v-if="pipelineTypeUpstream"
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)
"
/>
<ul
v-if="!isLoading"
:class="{
'inline js-has-linked-pipelines': hasTriggered || hasTriggeredBy,
}"
class="stage-column-list align-top"
>
<stage-column-component <stage-column-component
v-for="(stage, index) in graph" v-for="(stage, index) in graph"
:key="stage.name" :key="stage.name"
:class="{ :class="{
'append-right-48': shouldAddRightMargin(index), 'has-upstream prepend-left-64': hasUpstream(index),
'has-downstream': hasDownstream(index, graph.length),
'has-only-one-job': hasOnlyOneJob(stage),
'append-right-46': shouldAddRightMargin(index),
}" }"
:title="capitalizeStageName(stage.name)" :title="capitalizeStageName(stage.name)"
:groups="stage.groups" :groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)" :stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)" :is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action" :action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph" @refreshPipelineGraph="refreshPipelineGraph"
/> />
</ul> </ul>
<linked-pipelines-column
v-if="hasTriggered"
:linked-pipelines="triggeredPipelines"
:column-title="__('Downstream')"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
/>
<pipeline-graph
v-if="pipelineTypeDownstream"
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> </div>
</div> </div>
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import stageColumnMixin from 'ee_else_ce/pipelines/mixins/stage_column_mixin'; import stageColumnMixin from '../../mixins/stage_column_mixin';
import JobItem from './job_item.vue'; import JobItem from './job_item.vue';
import JobGroupDropdown from './job_group_dropdown.vue'; import JobGroupDropdown from './job_group_dropdown.vue';
import ActionComponent from './action_component.vue'; import ActionComponent from './action_component.vue';
......
import Flash from '~/flash'; import flash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default { export default {
methods: { methods: {
clickTriggeredByPipeline() {}, getExpandedPipelines(pipeline) {
clickTriggeredPipeline() {}, this.mediator.service
.getPipeline(this.mediator.getExpandedParameters())
.then(response => {
this.mediator.store.toggleLoading(pipeline);
this.mediator.store.storePipeline(response.data);
this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
})
.catch(() => {
this.mediator.store.toggleLoading(pipeline);
flash(__('An error occurred while fetching the pipeline.'));
});
},
/**
* Called when a linked pipeline is clicked.
*
* If the pipeline is collapsed we will start polling it & we will reset the other pipelines.
* If the pipeline is expanded we will close it.
*
* @param {String} method Method to fetch the pipeline
* @param {String} storeKey Store property that will be updates
* @param {String} resetStoreKey Store key for the visible pipeline that will need to be reset
* @param {Object} pipeline The clicked pipeline
*/
clickPipeline(parentPipeline, pipeline, openMethod, closeMethod) {
if (!pipeline.isExpanded) {
this.mediator.store[openMethod](parentPipeline, pipeline);
this.mediator.store.toggleLoading(pipeline);
this.mediator.poll.stop();
this.getExpandedPipelines(pipeline);
} else {
this.mediator.store[closeMethod](pipeline);
this.mediator.poll.stop();
this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
}
},
clickTriggeredByPipeline(parentPipeline, pipeline) {
this.clickPipeline(
parentPipeline,
pipeline,
'openTriggeredByPipeline',
'closeTriggeredByPipeline',
);
},
clickTriggeredPipeline(parentPipeline, pipeline) {
this.clickPipeline(
parentPipeline,
pipeline,
'openTriggeredPipeline',
'closeTriggeredPipeline',
);
},
requestRefreshPipelineGraph() { requestRefreshPipelineGraph() {
// When an action is clicked // When an action is clicked
// (wether in the dropdown or in the main nodes, we refresh the big graph) // (wether in the dropdown or in the main nodes, we refresh the big graph)
this.mediator this.mediator
.refreshPipeline() .refreshPipeline()
.catch(() => Flash(__('An error occurred while making the request.'))); .catch(() => flash(__('An error occurred while making the request.')));
}, },
}, },
}; };
export default { export default {
props: {
hasTriggeredBy: {
type: Boolean,
required: false,
default: false,
},
},
methods: { methods: {
buildConnnectorClass(index) { buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; return index === 0 && (!this.isFirstColumn || this.hasTriggeredBy) ? 'left-connector' : '';
}, },
}, },
}; };
...@@ -2,8 +2,8 @@ import Vue from 'vue'; ...@@ -2,8 +2,8 @@ import Vue from 'vue';
import Flash from '~/flash'; import Flash from '~/flash';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import { __ } from '~/locale'; import { __ } from '~/locale';
import pipelineGraph from 'ee_else_ce/pipelines/components/graph/graph_component.vue'; import pipelineGraph from './components/graph/graph_component.vue';
import GraphEEMixin from 'ee_else_ce/pipelines/mixins/graph_pipeline_bundle_mixin'; import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator'; import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue'; import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub'; import eventHub from './event_hub';
...@@ -23,7 +23,7 @@ export default () => { ...@@ -23,7 +23,7 @@ export default () => {
components: { components: {
pipelineGraph, pipelineGraph,
}, },
mixins: [GraphEEMixin], mixins: [GraphBundleMixin],
data() { data() {
return { return {
mediator, mediator,
......
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelineStore from 'ee_else_ce/pipelines/stores/pipeline_store'; import PipelineStore from './stores/pipeline_store';
import Flash from '../flash'; import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import { __ } from '../locale'; import { __ } from '../locale';
......
import Vue from 'vue';
import _ from 'underscore';
export default class PipelineStore { export default class PipelineStore {
constructor() { constructor() {
this.state = {}; this.state = {};
this.state.pipeline = {}; this.state.pipeline = {};
this.state.expandedPipelines = [];
} }
/**
* For the triggered pipelines adds the `isExpanded` key
*
* For the triggered_by pipeline adds the `isExpanded` key
* and saves it as an array
*
* @param {Object} pipeline
*/
storePipeline(pipeline = {}) { storePipeline(pipeline = {}) {
this.state.pipeline = pipeline; const pipelineCopy = Object.assign({}, pipeline);
if (pipelineCopy.triggered_by) {
pipelineCopy.triggered_by = [pipelineCopy.triggered_by];
const oldTriggeredBy =
this.state.pipeline &&
this.state.pipeline.triggered_by &&
this.state.pipeline.triggered_by[0];
this.parseTriggeredByPipelines(oldTriggeredBy, pipelineCopy.triggered_by[0]);
}
if (pipelineCopy.triggered && pipelineCopy.triggered.length) {
pipelineCopy.triggered.forEach(el => {
const oldPipeline =
this.state.pipeline &&
this.state.pipeline.triggered &&
this.state.pipeline.triggered.find(element => element.id === el.id);
this.parseTriggeredPipelines(oldPipeline, el);
});
}
this.state.pipeline = pipelineCopy;
}
/**
* Recursiverly parses the triggered by pipelines.
*
* Sets triggered_by as an array, there is always only 1 triggered_by pipeline.
* Adds key `isExpanding`
* Keeps old isExpading value due to polling
*
* @param {Array} parentPipeline
* @param {Object} pipeline
*/
parseTriggeredByPipelines(oldPipeline = {}, newPipeline) {
// keep old value in case it's opened because we're polling
Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
// add isLoading property
Vue.set(newPipeline, 'isLoading', false);
if (newPipeline.triggered_by) {
if (!_.isArray(newPipeline.triggered_by)) {
Object.assign(newPipeline, { triggered_by: [newPipeline.triggered_by] });
}
this.parseTriggeredByPipelines(oldPipeline, newPipeline.triggered_by[0]);
}
}
/**
* Recursively parses the triggered pipelines
* @param {Array} parentPipeline
* @param {Object} pipeline
*/
parseTriggeredPipelines(oldPipeline = {}, newPipeline) {
// keep old value in case it's opened because we're polling
Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
// add isLoading property
Vue.set(newPipeline, 'isLoading', false);
if (newPipeline.triggered && newPipeline.triggered.length > 0) {
newPipeline.triggered.forEach(el => {
const oldTriggered =
oldPipeline.triggered && oldPipeline.triggered.find(element => element.id === el.id);
this.parseTriggeredPipelines(oldTriggered, el);
});
}
}
/**
* Recursively resets all triggered by pipelines
*
* @param {Object} pipeline
*/
resetTriggeredByPipeline(parentPipeline, pipeline) {
parentPipeline.triggered_by.forEach(el => this.closePipeline(el));
if (pipeline.triggered_by && pipeline.triggered_by) {
this.resetTriggeredByPipeline(pipeline, pipeline.triggered_by);
}
}
/**
* Opens the clicked pipeline and closes all other ones.
* @param {Object} pipeline
*/
openTriggeredByPipeline(parentPipeline, pipeline) {
// first we need to reset all triggeredBy pipelines
this.resetTriggeredByPipeline(parentPipeline, pipeline);
this.openPipeline(pipeline);
}
/**
* On click, will close the given pipeline and all nested triggered by pipelines
*
* @param {Object} pipeline
*/
closeTriggeredByPipeline(pipeline) {
this.closePipeline(pipeline);
if (pipeline.triggered_by && pipeline.triggered_by.length) {
pipeline.triggered_by.forEach(triggeredBy => this.closeTriggeredByPipeline(triggeredBy));
}
}
/**
* Recursively closes all triggered pipelines for the given one.
*
* @param {Object} pipeline
*/
resetTriggeredPipelines(parentPipeline, pipeline) {
parentPipeline.triggered.forEach(el => this.closePipeline(el));
if (pipeline.triggered && pipeline.triggered.length) {
pipeline.triggered.forEach(el => this.resetTriggeredPipelines(pipeline, el));
}
}
/**
* Opens the clicked triggered pipeline and closes all other ones.
*
* @param {Object} pipeline
*/
openTriggeredPipeline(parentPipeline, pipeline) {
this.resetTriggeredPipelines(parentPipeline, pipeline);
this.openPipeline(pipeline);
}
/**
* On click, will close the given pipeline and all the nested triggered ones
* @param {Object} pipeline
*/
closeTriggeredPipeline(pipeline) {
this.closePipeline(pipeline);
if (pipeline.triggered && pipeline.triggered.length) {
pipeline.triggered.forEach(triggered => this.closeTriggeredPipeline(triggered));
}
}
/**
* Utility function, Closes the given pipeline
* @param {Object} pipeline
*/
closePipeline(pipeline) {
Vue.set(pipeline, 'isExpanded', false);
// remove the pipeline from the parameters
this.removeExpandedPipelineToRequestData(pipeline.id);
}
/**
* Utility function, Opens the given pipeline
* @param {Object} pipeline
*/
openPipeline(pipeline) {
Vue.set(pipeline, 'isExpanded', true);
// add the pipeline to the parameters
this.addExpandedPipelineToRequestData(pipeline.id);
}
// eslint-disable-next-line class-methods-use-this
toggleLoading(pipeline) {
Vue.set(pipeline, 'isLoading', !pipeline.isLoading);
}
addExpandedPipelineToRequestData(id) {
this.state.expandedPipelines.push(id);
}
removeExpandedPipelineToRequestData(id) {
this.state.expandedPipelines.splice(this.state.expandedPipelines.findIndex(el => el === id), 1);
} }
} }
---
title: Port over EE pipeline functionality to CE
merge_request: 18136
author:
type: changed
<script>
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';
export default {
name: 'PipelineGraph',
components: {
StageColumnComponent,
GlLoadingIcon,
LinkedPipelinesColumn,
},
mixins: [GraphMixin, GraphWidthMixin, GraphEEMixin],
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
isLinkedPipeline: {
type: Boolean,
required: false,
default: false,
},
mediator: {
type: Object,
required: true,
},
type: {
type: String,
required: false,
default: 'main',
},
},
upstream: 'upstream',
downstream: 'downstream',
data() {
return {
triggeredTopIndex: 1,
};
},
computed: {
hasTriggeredBy() {
return (
this.type !== this.$options.downstream &&
this.triggeredByPipelines &&
this.pipeline.triggered_by !== null
);
},
triggeredByPipelines() {
return this.pipeline.triggered_by;
},
hasTriggered() {
return (
this.type !== this.$options.upstream &&
this.triggeredPipelines &&
this.pipeline.triggered.length > 0
);
},
triggeredPipelines() {
return this.pipeline.triggered;
},
expandedTriggeredBy() {
return (
this.pipeline.triggered_by &&
_.isArray(this.pipeline.triggered_by) &&
this.pipeline.triggered_by.find(el => el.isExpanded)
);
},
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`;
},
},
methods: {
handleClickedDownstream(pipeline, clickedIndex) {
this.triggeredTopIndex = clickedIndex;
this.$emit('onClickTriggered', this.pipeline, pipeline);
},
hasOnlyOneJob(stage) {
return stage.groups.length === 1;
},
},
};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
<div
class="pipeline-visualization pipeline-graph"
:class="{ 'pipeline-tab-content': !isLinkedPipeline }"
>
<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"
/>
<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"
: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"
/>
<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>
import flash from '~/flash';
import { __ } from '~/locale';
export default {
methods: {
getExpandedPipelines(pipeline) {
this.mediator.service
.getPipeline(this.mediator.getExpandedParameters())
.then(response => {
this.mediator.store.toggleLoading(pipeline);
this.mediator.store.storePipeline(response.data);
this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
})
.catch(() => {
this.mediator.store.toggleLoading(pipeline);
flash(__('An error occurred while fetching the pipeline.'));
});
},
/**
* Called when a linked pipeline is clicked.
*
* If the pipeline is collapsed we will start polling it & we will reset the other pipelines.
* If the pipeline is expanded we will close it.
*
* @param {String} method Method to fetch the pipeline
* @param {String} storeKey Store property that will be updates
* @param {String} resetStoreKey Store key for the visible pipeline that will need to be reset
* @param {Object} pipeline The clicked pipeline
*/
clickPipeline(parentPipeline, pipeline, openMethod, closeMethod) {
if (!pipeline.isExpanded) {
this.mediator.store[openMethod](parentPipeline, pipeline);
this.mediator.store.toggleLoading(pipeline);
this.mediator.poll.stop();
this.getExpandedPipelines(pipeline);
} else {
this.mediator.store[closeMethod](pipeline);
this.mediator.poll.stop();
this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
}
},
clickTriggeredByPipeline(parentPipeline, pipeline) {
this.clickPipeline(
parentPipeline,
pipeline,
'openTriggeredByPipeline',
'closeTriggeredByPipeline',
);
},
clickTriggeredPipeline(parentPipeline, pipeline) {
this.clickPipeline(
parentPipeline,
pipeline,
'openTriggeredPipeline',
'closeTriggeredPipeline',
);
},
requestRefreshPipelineGraph() {
// When an action is clicked
// (wether in the dropdown or in the main nodes, we refresh the big graph)
this.mediator
.refreshPipeline()
.catch(() => flash(__('An error occurred while making the request.')));
},
},
};
export default {
props: {
hasTriggeredBy: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
buildConnnectorClass(index) {
return index === 0 && (!this.isFirstColumn || this.hasTriggeredBy) ? 'left-connector' : '';
},
},
};
import Vue from 'vue';
import _ from 'underscore';
import CePipelineStore from '~/pipelines/stores/pipeline_store';
/**
* Extends CE store with the logic to handle the upstream/downstream pipelines
*/
export default class PipelineStore extends CePipelineStore {
constructor() {
super();
this.state.expandedPipelines = [];
}
/**
* For the triggered pipelines adds the `isExpanded` key
*
* For the triggered_by pipeline adds the `isExpanded` key
* and saves it as an array
*
* @param {Object} pipeline
*/
storePipeline(pipeline = {}) {
const pipelineCopy = Object.assign({}, pipeline);
if (pipelineCopy.triggered_by) {
pipelineCopy.triggered_by = [pipelineCopy.triggered_by];
const oldTriggeredBy =
this.state.pipeline &&
this.state.pipeline.triggered_by &&
this.state.pipeline.triggered_by[0];
this.parseTriggeredByPipelines(oldTriggeredBy, pipelineCopy.triggered_by[0]);
}
if (pipelineCopy.triggered && pipelineCopy.triggered.length) {
pipelineCopy.triggered.forEach(el => {
const oldPipeline =
this.state.pipeline &&
this.state.pipeline.triggered &&
this.state.pipeline.triggered.find(element => element.id === el.id);
this.parseTriggeredPipelines(oldPipeline, el);
});
}
this.state.pipeline = pipelineCopy;
}
/**
* Recursiverly parses the triggered by pipelines.
*
* Sets triggered_by as an array, there is always only 1 triggered_by pipeline.
* Adds key `isExpanding`
* Keeps old isExpading value due to polling
*
* @param {Array} parentPipeline
* @param {Object} pipeline
*/
parseTriggeredByPipelines(oldPipeline = {}, newPipeline) {
// keep old value in case it's opened because we're polling
Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
// add isLoading property
Vue.set(newPipeline, 'isLoading', false);
if (newPipeline.triggered_by) {
if (!_.isArray(newPipeline.triggered_by)) {
Object.assign(newPipeline, { triggered_by: [newPipeline.triggered_by] });
}
this.parseTriggeredByPipelines(oldPipeline, newPipeline.triggered_by[0]);
}
}
/**
* Recursively parses the triggered pipelines
* @param {Array} parentPipeline
* @param {Object} pipeline
*/
parseTriggeredPipelines(oldPipeline = {}, newPipeline) {
// keep old value in case it's opened because we're polling
Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
// add isLoading property
Vue.set(newPipeline, 'isLoading', false);
if (newPipeline.triggered && newPipeline.triggered.length > 0) {
newPipeline.triggered.forEach(el => {
const oldTriggered =
oldPipeline.triggered && oldPipeline.triggered.find(element => element.id === el.id);
this.parseTriggeredPipelines(oldTriggered, el);
});
}
}
/**
* Recursively resets all triggered by pipelines
*
* @param {Object} pipeline
*/
resetTriggeredByPipeline(parentPipeline, pipeline) {
parentPipeline.triggered_by.forEach(el => this.closePipeline(el));
if (pipeline.triggered_by && pipeline.triggered_by) {
this.resetTriggeredByPipeline(pipeline, pipeline.triggered_by);
}
}
/**
* Opens the clicked pipeline and closes all other ones.
* @param {Object} pipeline
*/
openTriggeredByPipeline(parentPipeline, pipeline) {
// first we need to reset all triggeredBy pipelines
this.resetTriggeredByPipeline(parentPipeline, pipeline);
this.openPipeline(pipeline);
}
/**
* On click, will close the given pipeline and all nested triggered by pipelines
*
* @param {Object} pipeline
*/
closeTriggeredByPipeline(pipeline) {
this.closePipeline(pipeline);
if (pipeline.triggered_by && pipeline.triggered_by.length) {
pipeline.triggered_by.forEach(triggeredBy => this.closeTriggeredByPipeline(triggeredBy));
}
}
/**
* Recursively closes all triggered pipelines for the given one.
*
* @param {Object} pipeline
*/
resetTriggeredPipelines(parentPipeline, pipeline) {
parentPipeline.triggered.forEach(el => this.closePipeline(el));
if (pipeline.triggered && pipeline.triggered.length) {
pipeline.triggered.forEach(el => this.resetTriggeredPipelines(pipeline, el));
}
}
/**
* Opens the clicked triggered pipeline and closes all other ones.
*
* @param {Object} pipeline
*/
openTriggeredPipeline(parentPipeline, pipeline) {
this.resetTriggeredPipelines(parentPipeline, pipeline);
this.openPipeline(pipeline);
}
/**
* On click, will close the given pipeline and all the nested triggered ones
* @param {Object} pipeline
*/
closeTriggeredPipeline(pipeline) {
this.closePipeline(pipeline);
if (pipeline.triggered && pipeline.triggered.length) {
pipeline.triggered.forEach(triggered => this.closeTriggeredPipeline(triggered));
}
}
/**
* Utility function, Closes the given pipeline
* @param {Object} pipeline
*/
closePipeline(pipeline) {
Vue.set(pipeline, 'isExpanded', false);
// remove the pipeline from the parameters
this.removeExpandedPipelineToRequestData(pipeline.id);
}
/**
* Utility function, Opens the given pipeline
* @param {Object} pipeline
*/
openPipeline(pipeline) {
Vue.set(pipeline, 'isExpanded', true);
// add the pipeline to the parameters
this.addExpandedPipelineToRequestData(pipeline.id);
}
// eslint-disable-next-line class-methods-use-this
toggleLoading(pipeline) {
Vue.set(pipeline, 'isLoading', !pipeline.isLoading);
}
addExpandedPipelineToRequestData(id) {
this.state.expandedPipelines.push(id);
}
removeExpandedPipelineToRequestData(id) {
this.state.expandedPipelines.splice(this.state.expandedPipelines.findIndex(el => el === id), 1);
}
}
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import PipelineStore from 'ee/pipelines/stores/pipeline_store';
import graphComponent from 'ee/pipelines/components/graph/graph_component.vue';
import linkedPipelineJSON from 'ee_spec/pipelines/linked_pipelines_mock.json';
import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
import graphJSON from 'spec/pipelines/graph/mock_data';
describe('graph component', () => {
const GraphComponent = Vue.extend(graphComponent);
const store = new PipelineStore();
store.storePipeline(linkedPipelineJSON);
const mediator = new PipelinesMediator({ endpoint: '' });
let component;
beforeEach(() => {
setFixtures(`
<div class="layout-page"></div>
`);
});
afterEach(() => {
component.$destroy();
});
describe('while is loading', () => {
it('should render a loading icon', () => {
component = mountComponent(GraphComponent, {
isLoading: true,
pipeline: {},
mediator,
});
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
});
});
describe('when linked pipelines are present', () => {
beforeEach(() => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
});
describe('rendered output', () => {
it('should include the pipelines graph', () => {
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
});
it('should not include the loading icon', () => {
expect(component.$el.querySelector('.fa-spinner')).toBeNull();
});
it('should include the stage column list', () => {
expect(component.$el.querySelector('.stage-column-list')).not.toBeNull();
});
it('should include the no-margin class on the first child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('no-margin')).toEqual(true);
});
it('should include the has-only-one-job class on the first child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('has-only-one-job')).toEqual(true);
});
it('should include the left-margin class on the second child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column:last-child',
);
expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true);
});
it('should include the js-has-linked-pipelines flag', () => {
expect(component.$el.querySelector('.js-has-linked-pipelines')).not.toBeNull();
});
});
describe('computeds and methods', () => {
describe('capitalizeStageName', () => {
it('it capitalizes the stage name', () => {
expect(component.capitalizeStageName('mystage')).toBe('Mystage');
});
});
describe('stageConnectorClass', () => {
it('it returns left-margin when there is a triggerer', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
});
});
describe('linked pipelines components', () => {
beforeEach(() => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
});
it('should render an upstream pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(component.$el.innerHTML).toContain('Upstream');
});
it('should render a downstream pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(component.$el.innerHTML).toContain('Downstream');
});
describe('triggered by', () => {
describe('on click', () => {
it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => {
spyOn(component, '$emit');
component.$el.querySelector('#js-linked-pipeline-12').click();
expect(component.$emit).toHaveBeenCalledWith(
'onClickTriggeredBy',
component.pipeline,
component.pipeline.triggered_by[0],
);
});
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered_by[0].isExpanded = true;
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
Vue.nextTick()
.then(() => {
expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
describe('triggered', () => {
describe('on click', () => {
it('should emit `onClickTriggered`', () => {
spyOn(component, '$emit');
component.$el.querySelector('#js-linked-pipeline-34993051').click();
expect(component.$emit).toHaveBeenCalledWith(
'onClickTriggered',
component.pipeline,
component.pipeline.triggered[0],
);
});
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered[0].isExpanded = true;
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
Vue.nextTick()
.then(() => {
expect(
component.$el.querySelector('.js-downstream-pipeline-34993051'),
).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
});
});
describe('when linked pipelines are not present', () => {
beforeEach(() => {
const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline,
mediator,
});
});
describe('rendered output', () => {
it('should include the first column with a no margin', () => {
const firstColumn = component.$el.querySelector('.stage-column:first-child');
expect(firstColumn.classList.contains('no-margin')).toEqual(true);
});
it('should not render a linked pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).toBeNull();
});
});
describe('stageConnectorClass', () => {
it('it returns left-margin when no triggerer and there is one job', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
it('it returns left-margin when no triggerer and not the first stage', () => {
expect(component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin');
});
});
});
describe('capitalizeStageName', () => {
it('capitalizes and escapes stage name', () => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: graphJSON,
mediator,
});
expect(
component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim(),
).toEqual('Deploy &lt;img src=x onerror=alert(document.domain)&gt;');
});
});
});
...@@ -2,8 +2,8 @@ import Vue from 'vue'; ...@@ -2,8 +2,8 @@ import Vue from 'vue';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from 'ee_spec/vue_mr_widget/mock_data'; import mockData from 'ee_spec/vue_mr_widget/mock_data';
import mockLinkedPipelines from 'ee_spec/pipelines/graph/linked_pipelines_mock_data';
import { trimText } from 'spec/helpers/text_helper'; import { trimText } from 'spec/helpers/text_helper';
import mockLinkedPipelines from '../vue_shared/components/linked_pipelines_mock_data';
describe('MRWidgetPipeline', () => { describe('MRWidgetPipeline', () => {
let vm; let vm;
......
import Vue from 'vue'; import Vue from 'vue';
import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue'; import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
import mockData from 'ee_spec/pipelines/graph/linked_pipelines_mock_data'; import mockData from './linked_pipelines_mock_data';
const ListComponent = Vue.extend(LinkedPipelinesMiniList); const ListComponent = Vue.extend(LinkedPipelinesMiniList);
......
...@@ -74,5 +74,3 @@ module QA::Page ...@@ -74,5 +74,3 @@ module QA::Page
end end
end end
end end
QA::Page::Project::Pipeline::Show.prepend_if_ee('QA::EE::Page::Project::Pipeline::Show')
import Vue from 'vue'; import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import PipelineStore from '~/pipelines/stores/pipeline_store';
import graphComponent from '~/pipelines/components/graph/graph_component.vue'; import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import graphJSON from './mock_data'; import graphJSON from './mock_data';
import linkedPipelineJSON from '../linked_pipelines_mock.json';
import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
describe('graph component', () => { describe('graph component', () => {
const GraphComponent = Vue.extend(graphComponent); const GraphComponent = Vue.extend(graphComponent);
const store = new PipelineStore();
store.storePipeline(linkedPipelineJSON);
const mediator = new PipelinesMediator({ endpoint: '' });
let component; let component;
beforeEach(() => { beforeEach(() => {
...@@ -22,6 +29,7 @@ describe('graph component', () => { ...@@ -22,6 +29,7 @@ describe('graph component', () => {
component = mountComponent(GraphComponent, { component = mountComponent(GraphComponent, {
isLoading: true, isLoading: true,
pipeline: {}, pipeline: {},
mediator,
}); });
expect(component.$el.querySelector('.loading-icon')).toBeDefined(); expect(component.$el.querySelector('.loading-icon')).toBeDefined();
...@@ -33,6 +41,7 @@ describe('graph component', () => { ...@@ -33,6 +41,7 @@ describe('graph component', () => {
component = mountComponent(GraphComponent, { component = mountComponent(GraphComponent, {
isLoading: false, isLoading: false,
pipeline: graphJSON, pipeline: graphJSON,
mediator,
}); });
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
...@@ -57,11 +66,205 @@ describe('graph component', () => { ...@@ -57,11 +66,205 @@ describe('graph component', () => {
}); });
}); });
describe('when linked pipelines are present', () => {
beforeEach(() => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
});
describe('rendered output', () => {
it('should include the pipelines graph', () => {
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
});
it('should not include the loading icon', () => {
expect(component.$el.querySelector('.fa-spinner')).toBeNull();
});
it('should include the stage column list', () => {
expect(component.$el.querySelector('.stage-column-list')).not.toBeNull();
});
it('should include the no-margin class on the first child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('no-margin')).toEqual(true);
});
it('should include the has-only-one-job class on the first child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('has-only-one-job')).toEqual(true);
});
it('should include the left-margin class on the second child', () => {
const firstStageColumnElement = component.$el.querySelector(
'.stage-column-list .stage-column:last-child',
);
expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true);
});
it('should include the js-has-linked-pipelines flag', () => {
expect(component.$el.querySelector('.js-has-linked-pipelines')).not.toBeNull();
});
});
describe('computeds and methods', () => {
describe('capitalizeStageName', () => {
it('it capitalizes the stage name', () => {
expect(component.capitalizeStageName('mystage')).toBe('Mystage');
});
});
describe('stageConnectorClass', () => {
it('it returns left-margin when there is a triggerer', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
});
});
describe('linked pipelines components', () => {
beforeEach(() => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
});
it('should render an upstream pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(component.$el.innerHTML).toContain('Upstream');
});
it('should render a downstream pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(component.$el.innerHTML).toContain('Downstream');
});
describe('triggered by', () => {
describe('on click', () => {
it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => {
spyOn(component, '$emit');
component.$el.querySelector('#js-linked-pipeline-12').click();
expect(component.$emit).toHaveBeenCalledWith(
'onClickTriggeredBy',
component.pipeline,
component.pipeline.triggered_by[0],
);
});
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered_by[0].isExpanded = true;
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
Vue.nextTick()
.then(() => {
expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
describe('triggered', () => {
describe('on click', () => {
it('should emit `onClickTriggered`', () => {
spyOn(component, '$emit');
component.$el.querySelector('#js-linked-pipeline-34993051').click();
expect(component.$emit).toHaveBeenCalledWith(
'onClickTriggered',
component.pipeline,
component.pipeline.triggered[0],
);
});
});
describe('with expanded pipeline', () => {
it('should render expanded pipeline', done => {
// expand the pipeline
store.state.pipeline.triggered[0].isExpanded = true;
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: store.state.pipeline,
mediator,
});
Vue.nextTick()
.then(() => {
expect(
component.$el.querySelector('.js-downstream-pipeline-34993051'),
).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
});
});
});
});
describe('when linked pipelines are not present', () => {
beforeEach(() => {
const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline,
mediator,
});
});
describe('rendered output', () => {
it('should include the first column with a no margin', () => {
const firstColumn = component.$el.querySelector('.stage-column:first-child');
expect(firstColumn.classList.contains('no-margin')).toEqual(true);
});
it('should not render a linked pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).toBeNull();
});
});
describe('stageConnectorClass', () => {
it('it returns left-margin when no triggerer and there is one job', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
it('it returns left-margin when no triggerer and not the first stage', () => {
expect(component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin');
});
});
});
describe('capitalizeStageName', () => { describe('capitalizeStageName', () => {
it('capitalizes and escapes stage name', () => { it('capitalizes and escapes stage name', () => {
component = mountComponent(GraphComponent, { component = mountComponent(GraphComponent, {
isLoading: false, isLoading: false,
pipeline: graphJSON, pipeline: graphJSON,
mediator,
}); });
expect( expect(
......
import Vue from 'vue'; import Vue from 'vue';
import LinkedPipelineComponent from 'ee/pipelines/components/graph/linked_pipeline.vue'; import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './linked_pipelines_mock_data'; import mockData from './linked_pipelines_mock_data';
......
import Vue from 'vue'; import Vue from 'vue';
import LinkedPipelinesColumn from 'ee/pipelines/components/graph/linked_pipelines_column.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './linked_pipelines_mock_data'; import mockData from './linked_pipelines_mock_data';
......
export default {
triggered_by: {
id: 129,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/129',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/129',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: '7-5-stable',
path: '/gitlab-org/gitlab-foss/commits/7-5-stable',
tag: false,
branch: true,
},
commit: {
id: '23433d4d8b20d7e45c103d0b6048faad38a130ab',
short_id: '23433d4d',
title: 'Version 7.5.0.rc1',
created_at: '2014-11-17T15:44:14.000+01:00',
parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'],
message: 'Version 7.5.0.rc1\n',
author_name: 'Jacob Vosmaer',
author_email: 'contact@jacobvosmaer.nl',
authored_date: '2014-11-17T15:44:14.000+01:00',
committer_name: 'Jacob Vosmaer',
committer_email: 'contact@jacobvosmaer.nl',
committed_date: '2014-11-17T15:44:14.000+01:00',
author_gravatar_url:
'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/129/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/129/cancel',
created_at: '2017-05-24T14:46:20.090Z',
updated_at: '2017-05-24T14:46:29.906Z',
},
triggered: [
{
id: 132,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/132',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/132',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
short_id: 'b9d58c4c',
title: 'getting user keys publically through http without any authentication, the github…',
created_at: '2013-10-03T12:50:33.000+05:30',
parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
message:
'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
author_name: 'devaroop',
author_email: 'devaroop123@yahoo.co.in',
authored_date: '2013-10-02T20:39:29.000+05:30',
committer_name: 'devaroop',
committer_email: 'devaroop123@yahoo.co.in',
committed_date: '2013-10-03T12:50:33.000+05:30',
author_gravatar_url:
'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel',
created_at: '2017-05-24T14:46:24.644Z',
updated_at: '2017-05-24T14:48:55.226Z',
},
{
id: 133,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/133',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/133',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
short_id: 'b6bd4856',
title: 'getting user keys publically through http without any authentication, the github…',
created_at: '2013-10-02T20:39:29.000+05:30',
parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
message:
'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
author_name: 'devaroop',
author_email: 'devaroop123@yahoo.co.in',
authored_date: '2013-10-02T20:39:29.000+05:30',
committer_name: 'devaroop',
committer_email: 'devaroop123@yahoo.co.in',
committed_date: '2013-10-02T20:39:29.000+05:30',
author_gravatar_url:
'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel',
created_at: '2017-05-24T14:46:24.648Z',
updated_at: '2017-05-24T14:48:59.673Z',
},
{
id: 130,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/130',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/130',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
short_id: '6d7ced4a',
title: 'Whitespace fixes to patch',
created_at: '2013-10-08T13:53:22.000-05:00',
parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
message: 'Whitespace fixes to patch\n',
author_name: 'Dale Hamel',
author_email: 'dale.hamel@srvthe.net',
authored_date: '2013-10-08T13:53:22.000-05:00',
committer_name: 'Dale Hamel',
committer_email: 'dale.hamel@invenia.ca',
committed_date: '2013-10-08T13:53:22.000-05:00',
author_gravatar_url:
'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel',
created_at: '2017-05-24T14:46:24.630Z',
updated_at: '2017-05-24T14:49:45.091Z',
},
{
id: 131,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/132',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/132',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
short_id: 'b9d58c4c',
title: 'getting user keys publically through http without any authentication, the github…',
created_at: '2013-10-03T12:50:33.000+05:30',
parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
message:
'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
author_name: 'devaroop',
author_email: 'devaroop123@yahoo.co.in',
authored_date: '2013-10-02T20:39:29.000+05:30',
committer_name: 'devaroop',
committer_email: 'devaroop123@yahoo.co.in',
committed_date: '2013-10-03T12:50:33.000+05:30',
author_gravatar_url:
'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel',
created_at: '2017-05-24T14:46:24.644Z',
updated_at: '2017-05-24T14:48:55.226Z',
},
{
id: 134,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/133',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/133',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
short_id: 'b6bd4856',
title: 'getting user keys publically through http without any authentication, the github…',
created_at: '2013-10-02T20:39:29.000+05:30',
parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
message:
'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
author_name: 'devaroop',
author_email: 'devaroop123@yahoo.co.in',
authored_date: '2013-10-02T20:39:29.000+05:30',
committer_name: 'devaroop',
committer_email: 'devaroop123@yahoo.co.in',
committed_date: '2013-10-02T20:39:29.000+05:30',
author_gravatar_url:
'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel',
created_at: '2017-05-24T14:46:24.648Z',
updated_at: '2017-05-24T14:48:59.673Z',
},
{
id: 135,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/130',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/130',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: 'crowd',
path: '/gitlab-org/gitlab-foss/commits/crowd',
tag: false,
branch: true,
},
commit: {
id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
short_id: '6d7ced4a',
title: 'Whitespace fixes to patch',
created_at: '2013-10-08T13:53:22.000-05:00',
parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
message: 'Whitespace fixes to patch\n',
author_name: 'Dale Hamel',
author_email: 'dale.hamel@srvthe.net',
authored_date: '2013-10-08T13:53:22.000-05:00',
committer_name: 'Dale Hamel',
committer_email: 'dale.hamel@invenia.ca',
committed_date: '2013-10-08T13:53:22.000-05:00',
author_gravatar_url:
'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel',
created_at: '2017-05-24T14:46:24.630Z',
updated_at: '2017-05-24T14:49:45.091Z',
},
],
};
import PipelineStore from 'ee/pipelines/stores/pipeline_store'; import PipelineStore from '~/pipelines/stores/pipeline_store';
import LinkedPipelines from '../linked_pipelines_mock.json'; import LinkedPipelines from '../linked_pipelines_mock.json';
describe('EE Pipeline store', () => { describe('EE Pipeline store', () => {
......
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