Commit 63305cfe authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'bpj-related-pipelines-graph' into 'master'

Frontend for cross-project pipeline triggers

See merge request !1985
parents 091657d4 7680f95d
<script>
import linkedPipelinesColumn from './linked_pipelines_column.vue';
import stageColumnComponent from './stage_column_component.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import '../../../flash';
export default {
props: {
......@@ -16,6 +16,7 @@
},
components: {
linkedPipelinesColumn,
stageColumnComponent,
loadingIcon,
},
......@@ -24,6 +25,18 @@
graph() {
return this.pipeline.details && this.pipeline.details.stages;
},
triggered() {
return this.pipeline.triggered || [];
},
triggeredBy() {
return this.pipeline.triggeredBy || [];
},
hasTriggered() {
return !!this.triggered.length;
},
hasTriggeredBy() {
return !!this.triggeredBy.length;
},
},
methods: {
......@@ -61,17 +74,42 @@
/>
</div>
<linked-pipelines-column
v-if="hasTriggeredBy"
:linked-pipelines="triggeredBy"
column-title="Upstream"
graph-position="left"
/>
<ul
v-if="!isLoading"
class="stage-column-list">
class="stage-column-list"
:class="{
'has-linked-pipelines': hasTriggered || hasTriggeredBy
}"
>
<stage-column-component
v-for="(stage, index) in graph"
:class="{
'has-upstream': index === 0 && hasTriggeredBy,
'has-downstream': index === graph.length - 1 && hasTriggered,
'has-only-one-job': stage.groups.length === 1
}"
:title="capitalizeStageName(stage.name)"
:jobs="stage.groups"
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"/>
:is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
/>
</ul>
<linked-pipelines-column
v-if="hasTriggered"
:linked-pipelines="triggered"
column-title="Downstream"
graph-position="right"
/>
</div>
</div>
</template>
<script>
import ciStatus from '../../../vue_shared/components/ci_icon.vue';
import tooltipMixin from '../../../vue_shared/mixins/tooltip';
export default {
props: {
pipelineId: {
type: Number,
required: true,
},
pipelinePath: {
type: String,
required: true,
},
pipelineStatus: {
type: Object,
required: true,
},
projectName: {
type: String,
required: true,
},
},
mixins: [
tooltipMixin,
],
components: {
ciStatus,
},
computed: {
tooltipText() {
return `${this.projectName} - ${this.pipelineStatus.label}`;
},
},
};
</script>
<template>
<li class="linked-pipeline build">
<div class="curve"></div>
<div>
<a
class="linked-pipeline-content"
:href="pipelinePath"
:title="tooltipText"
ref="tooltip"
data-toggle="tooltip"
data-container="body">
<span class="linked-pipeline-status ci-status-text">
<ci-status :status="pipelineStatus"/>
</span>
<span class="linked-pipeline-project-name">{{ projectName }}</span>
<span class="project-name-pipeline-id-separator">&#8226;</span>
<span class="linked-pipeline-id">#{{ pipelineId }}</span>
</a>
</div>
</li>
</template>
<script>
import linkedPipeline from './linked_pipeline.vue';
export default {
props: {
columnTitle: {
type: String,
required: true,
},
linkedPipelines: {
type: Array,
required: true,
},
graphPosition: {
type: String,
required: true,
},
},
components: {
linkedPipeline,
},
computed: {
columnClass() {
return `graph-position-${this.graphPosition}`;
},
},
};
</script>
<template>
<div
class="stage-column linked-pipelines-column"
:class="columnClass"
>
<div class="stage-name linked-pipelines-column-title"> {{ columnTitle }} </div>
<div class="cross-project-triangle"></div>
<ul>
<linked-pipeline
v-for="(pipeline, index) in linkedPipelines"
:class="{
'flat-connector-before': index === 0 && graphPosition === 'right'
}"
:key="pipeline.id"
:pipeline-id="pipeline.id"
:project-name="pipeline.project.name"
:pipeline-status="pipeline.details.status"
:pipeline-path="pipeline.path"
/>
</ul>
</div>
</template>
......@@ -25,6 +25,10 @@ export default {
required: false,
default: '',
},
hasTriggeredBy: {
type: Boolean,
required: true,
},
},
components: {
......@@ -40,10 +44,6 @@ export default {
jobId(job) {
return `ci-badge-${job.name}`;
},
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
},
};
</script>
......@@ -60,7 +60,9 @@ export default {
v-for="(job, index) in jobs"
:key="job.id"
class="build"
:class="buildConnnectorClass(index)"
:class="{
'left-connector': index === 0 && (!isFirstColumn || hasTriggeredBy)
}"
:id="jobId(job)">
<div class="curve"></div>
......
......@@ -21,6 +21,7 @@
* - Jobs table
* - Jobs show view header
* - Jobs show view sidebar
* - Linked pipelines
*/
export default {
props: {
......
......@@ -250,6 +250,7 @@ $dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777;
$file-mode-changed: #777;
$diff-image-bg: #ddd;
$diff-image-info-color: grey;
$diff-swipe-border: #999;
$diff-view-modes-color: grey;
......@@ -581,3 +582,10 @@ $gl-gold-plan: #d4af37;
$gl-silver-plan: #91a1ab;
$gl-bronze-plan: #cd7f32;
$gl-no-plan: $gl-gray-light;
/*
Cross-project Pipelines
*/
$linked-project-column-margin: 60px;
@mixin flat-connector-before($length: 44px) {
&::before {
content: '';
position: absolute;
top: 48%;
left: -$length;
border-top: 2px solid $border-color;
width: $length;
height: 1px;
}
}
@mixin build-content($border-radius: 30px) {
display: inline-block;
padding: 8px 10px 9px;
width: 100%;
border: 1px solid $border-color;
border-radius: $border-radius;
background-color: $white-light;
&:hover {
background-color: $stage-hover-bg;
border: 1px solid $stage-hover-border;
color: $gl-text-color;
}
}
.pipelines {
.stage {
max-width: 90px;
......@@ -377,15 +404,7 @@
margin-left: 44px;
.left-connector {
&::before {
content: '';
position: absolute;
top: 48%;
left: -44px;
border-top: 2px solid $border-color;
width: 44px;
height: 1px;
}
@include flat-connector-before;
}
}
}
......@@ -398,7 +417,8 @@
list-style: none;
}
&:last-child {
// when downstream pipelines are present, the last stage isn't the last column
&:last-child:not(.has-downstream) {
.build {
// Remove right connecting horizontal line from first build in last stage
&:first-child {
......@@ -421,7 +441,8 @@
}
}
&:first-child {
// when upstream pipelines are present, the first stage isn't the first column
&:first-child:not(.has-upstream) {
.build {
// Remove left curved connectors from all builds in first stage
&:not(:first-child) {
......@@ -539,21 +560,9 @@
}
.build-content {
display: inline-block;
padding: 8px 10px 9px;
width: 100%;
border: 1px solid $border-color;
border-radius: 30px;
background-color: $white-light;
&:hover {
background-color: $stage-hover-bg;
border: 1px solid $stage-hover-border;
color: $gl-text-color;
}
@include build-content();
}
// Connect first build in each stage with right horizontal line
&:first-child {
&::after {
......@@ -984,3 +993,115 @@
width: 12px;
}
}
/**
* Cross-project pipelines (applied conditionally to pipeline graph)
*/
.has-linked-pipelines.stage-column-list {
display: inline-block;
}
.linked-pipelines-column.stage-column {
position: relative;
& > ul {
padding: 0;
}
&.graph-position-left {
margin-right: 36px;
.cross-project-triangle {
right: -42px;
}
}
&.graph-position-right {
margin-left: 60px;
.cross-project-triangle {
left: -64px;
}
}
.linked-pipeline.build {
height: 40px;
// apply custom dimensions to connector before and after for triangle arrow
&.flat-connector-before {
@include flat-connector-before($linked-project-column-margin);
}
&::after {
right: -$linked-project-column-margin;
width: $linked-project-column-margin;
}
.linked-pipeline-content {
@include build-content(0);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.stage-column.has-upstream {
margin-left: 60px;
.left-connector {
@include flat-connector-before(60px)
}
&.has-only-one-job {
margin-left: 30px;
margin-right: 0;
.left-connector {
@include flat-connector-before;
}
}
}
.stage-column.has-downstream {
margin-right: $linked-project-column-margin;
&.has-only-one-job:not(:first-child) {
margin-right: 36px;
margin-left: 0;
.left-connector {
@include flat-connector-before;
}
}
.build {
&:first-child {
&::after {
right: -$linked-project-column-margin;
width: $linked-project-column-margin;
}
}
}
}
.cross-project-triangle {
position: absolute;
top: 48px;
width: 0;
height: 0;
border-bottom: 7px solid transparent;
border-top: 7px solid transparent;
border-left: 7px solid $gray-darkest;
font-size: 0;
line-height: 0;
z-index: 10;
}
.project-name-pipeline-id-separator {
display: inline-block;
margin: 4px 2px 0;
font-size: 10px;
vertical-align: top;
}
import Vue from 'vue';
import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import graphJSON from './mock_data';
import linkedPipelineJSON from './linked_pipelines_mock_data';
describe('graph component', () => {
preloadFixtures('static/graph.html.raw');
const GraphComponent = Vue.extend(graphComponent);
let GraphComponent;
const pipelineJSON = Object.assign(graphJSON, {
triggered: linkedPipelineJSON.triggered,
triggeredBy: linkedPipelineJSON.triggered_by,
});
beforeEach(() => {
loadFixtures('static/graph.html.raw');
GraphComponent = Vue.extend(graphComponent);
});
const defaultPropsData = {
pipeline: pipelineJSON,
isLoading: false,
};
describe('graph component', function () {
describe('while is loading', function () {
beforeEach(function () {
this.component = new GraphComponent({
propsData: { pipeline: {}, isLoading: true },
}).$mount();
});
describe('while is loading', () => {
it('should render a loading icon', () => {
const component = new GraphComponent({
propsData: {
isLoading: true,
pipeline: {},
},
}).$mount('#js-pipeline-graph-vue');
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
it('should render a loading icon', function () {
expect(this.component.$el.querySelector('.fa-spinner')).not.toBeNull();
});
});
describe('with data', () => {
it('should render the graph', () => {
const component = new GraphComponent({
propsData: {
isLoading: false,
pipeline: graphJSON,
},
}).$mount('#js-pipeline-graph-vue');
describe('when linked pipelines are present', function () {
beforeEach(function () {
this.component = new GraphComponent({
propsData: defaultPropsData,
}).$mount();
});
describe('rendered output', function () {
it('should include the pipelines graph', function () {
expect(this.component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
});
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
it('should not include the loading icon', function () {
expect(this.component.$el.querySelector('.fa-spinner')).toBeNull();
});
expect(
component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
).toEqual(true);
it('should include the stage column list', function () {
expect(this.component.$el.querySelector('.stage-column-list')).not.toBeNull();
});
it('should include the no-margin class on the first child', function () {
const firstStageColumnElement = this.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', function () {
const firstStageColumnElement = this.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', function () {
const firstStageColumnElement = this.component.$el.querySelector('.stage-column-list .stage-column:last-child');
expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true);
});
it('should include the has-linked-pipelines flag', function () {
expect(this.component.$el.querySelector('.has-linked-pipelines')).not.toBeNull();
});
});
expect(
component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
).toEqual(true);
describe('computeds and methods', function () {
describe('capitalizeStageName', function () {
it('it capitalizes the stage name', function () {
expect(this.component.capitalizeStageName('mystage')).toBe('Mystage');
});
});
expect(
component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
).toEqual(true);
describe('stageConnectorClass', function () {
it('it returns left-margin when there is a triggerer', function () {
expect(this.component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
});
});
describe('linked pipelines components', function () {
it('should render an upstream pipelines column', function () {
expect(this.component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(this.component.$el.innerHTML).toContain('Upstream');
});
it('should render a downstream pipelines column', function () {
expect(this.component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
expect(this.component.$el.innerHTML).toContain('Downstream');
});
});
});
describe('when linked pipelines are not present', function () {
beforeEach(function () {
const pipeline = Object.assign(graphJSON, { triggered: [], triggeredBy: [] });
this.component = new GraphComponent({
propsData: { pipeline, isLoading: false },
}).$mount();
});
describe('rendered output', function () {
it('should include the first column with a no margin', function () {
const firstColumn = this.component.$el.querySelector('.stage-column:first-child');
expect(firstColumn.classList.contains('no-margin')).toEqual(true);
});
it('should not render a linked pipelines column', function () {
expect(this.component.$el.querySelector('.linked-pipelines-column')).toBeNull();
});
});
expect(component.$el.querySelector('loading-icon')).toBe(null);
describe('stageConnectorClass', function () {
it('it returns left-margin when no triggerer and there is one job', function () {
expect(this.component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
});
expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
it('it returns left-margin when no triggerer and not the first stage', function () {
expect(this.component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin');
});
});
});
});
import Vue from 'vue';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import mockData from './linked_pipelines_mock_data';
const LinkedPipeline = Vue.extend(LinkedPipelineComponent);
const mockPipeline = mockData.triggered[0];
describe('Linked pipeline', () => {
beforeEach(() => {
this.propsData = {
pipelineId: mockPipeline.id,
pipelinePath: mockPipeline.path,
pipelineStatus: mockPipeline.details.status,
projectName: mockPipeline.project.name,
};
this.linkedPipeline = new LinkedPipeline({
propsData: this.propsData,
}).$mount();
});
it('should return a defined Vue component', () => {
expect(this.linkedPipeline).toBeDefined();
});
it('should render a list item as the containing element', () => {
expect(this.linkedPipeline.$el.tagName).toBe('LI');
});
it('should render a link', () => {
const linkElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-content');
expect(linkElement).not.toBeNull();
});
it('should link to the correct path', () => {
const linkElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-content');
expect(linkElement.getAttribute('href')).toBe(this.propsData.pipelinePath);
});
it('should render the project name', () => {
const projectNameElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-project-name');
expect(projectNameElement.innerText).toContain(this.propsData.projectName);
});
it('should render an svg within the status container', () => {
const pipelineStatusElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-status');
expect(pipelineStatusElement.querySelector('svg')).not.toBeNull();
});
it('should render the pipeline status icon svg', () => {
const pipelineStatusElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-status');
expect(pipelineStatusElement.querySelector('.ci-status-icon-running')).not.toBeNull();
expect(pipelineStatusElement.innerHTML).toContain('<svg');
});
it('should render the correct pipeline status icon style selector', () => {
const pipelineStatusElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-status');
expect(pipelineStatusElement.firstChild.classList.contains('ci-status-icon-running')).toBe(true);
});
it('should have a ci-status child component', () => {
const ciStatusComponent = this.linkedPipeline.$children[0];
expect(ciStatusComponent).toBeDefined();
expect(ciStatusComponent.$el.classList.contains('ci-status-icon')).toBe(true);
});
it('should render the pipeline id', () => {
const pipelineIdElement = this.linkedPipeline.$el.querySelector('.linked-pipeline-id');
expect(pipelineIdElement.innerText).toContain(`#${this.propsData.pipelineId}`);
});
it('should correctly compute the tooltip text', () => {
expect(this.linkedPipeline.tooltipText).toContain(mockPipeline.project.name);
expect(this.linkedPipeline.tooltipText).toContain(mockPipeline.details.status.label);
});
it('should render the tooltip text as the title attribute', () => {
const tooltipRef = this.linkedPipeline.$el.querySelector('.linked-pipeline-content');
const titleAttr = tooltipRef.getAttribute('data-original-title');
expect(titleAttr).toContain(mockPipeline.project.name);
expect(titleAttr).toContain(mockPipeline.details.status.label);
});
});
import Vue from 'vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import mockData from './linked_pipelines_mock_data';
const LinkedPipelinesColumnComponent = Vue.extend(LinkedPipelinesColumn);
describe('Linked Pipelines Column', () => {
beforeEach(() => {
this.propsData = {
columnTitle: 'Upstream',
linkedPipelines: mockData.triggered,
graphPosition: 'right',
};
this.linkedPipelinesColumn = new LinkedPipelinesColumnComponent({
propsData: this.propsData,
}).$mount();
});
it('instantiates a defined Vue component', () => {
expect(this.linkedPipelinesColumn).toBeDefined();
});
it('renders the pipeline orientation', () => {
const titleElement = this.linkedPipelinesColumn.$el.querySelector('.linked-pipelines-column-title');
expect(titleElement.innerText).toContain(this.propsData.columnTitle);
});
it('has the correct number of linked pipeline child components', () => {
expect(this.linkedPipelinesColumn.$children.length).toBe(this.propsData.linkedPipelines.length);
});
it('renders the correct number of linked pipelines', () => {
const linkedPipelineElements = this.linkedPipelinesColumn.$el.querySelectorAll('.linked-pipeline');
expect(linkedPipelineElements.length).toBe(this.propsData.linkedPipelines.length);
});
});
/* eslint-disable quote-props, quotes, comma-dangle */
export default {
"triggered_by": [
{
"id": 129,
"active": true,
"path": "/gitlab-org/gitlab-ce/pipelines/129",
"project": {
"name": "GitLabCE"
},
"details": {
"status": {
"icon": "icon_status_running",
"text": "running",
"label": "running",
"group": "running",
"has_details": true,
"details_path": "/gitlab-org/gitlab-ce/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-ce/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-ce/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab",
"commit_path": "/gitlab-org/gitlab-ce/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab"
},
"retry_path": "/gitlab-org/gitlab-ce/pipelines/129/retry",
"cancel_path": "/gitlab-org/gitlab-ce/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-ce/pipelines/132",
"project": {
"name": "GitLabCE"
},
"details": {
"status": {
"icon": "icon_status_running",
"text": "running",
"label": "running",
"group": "running",
"has_details": true,
"details_path": "/gitlab-org/gitlab-ce/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-ce/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-ce/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e",
"commit_path": "/gitlab-org/gitlab-ce/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e"
},
"retry_path": "/gitlab-org/gitlab-ce/pipelines/132/retry",
"cancel_path": "/gitlab-org/gitlab-ce/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-ce/pipelines/133",
"project": {
"name": "GitLabCE"
},
"details": {
"status": {
"icon": "icon_status_running",
"text": "running",
"label": "running",
"group": "running",
"has_details": true,
"details_path": "/gitlab-org/gitlab-ce/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-ce/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-ce/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b",
"commit_path": "/gitlab-org/gitlab-ce/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b"
},
"retry_path": "/gitlab-org/gitlab-ce/pipelines/133/retry",
"cancel_path": "/gitlab-org/gitlab-ce/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-ce/pipelines/130",
"project": {
"name": "GitLabCE"
},
"details": {
"status": {
"icon": "icon_status_running",
"text": "running",
"label": "running",
"group": "running",
"has_details": true,
"details_path": "/gitlab-org/gitlab-ce/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-ce/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-ce/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f",
"commit_path": "/gitlab-org/gitlab-ce/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f"
},
"retry_path": "/gitlab-org/gitlab-ce/pipelines/130/retry",
"cancel_path": "/gitlab-org/gitlab-ce/pipelines/130/cancel",
"created_at": "2017-05-24T14:46:24.630Z",
"updated_at": "2017-05-24T14:49:45.091Z"
}
]
};
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