Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
d8646823
Commit
d8646823
authored
Nov 27, 2020
by
Sarah Groff Hennigh-Palermo
Committed by
Mark Florian
Nov 27, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create branch with component changes
Applies changes and adds legacy files, specs
parent
384cc5ed
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1403 additions
and
680 deletions
+1403
-680
app/assets/javascripts/pipelines/components/graph/graph_component.vue
...avascripts/pipelines/components/graph/graph_component.vue
+6
-150
app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
...pts/pipelines/components/graph/graph_component_legacy.vue
+270
-0
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
...pts/pipelines/components/graph/stage_column_component.vue
+1
-1
app/assets/javascripts/pipelines/components/graph/utils.js
app/assets/javascripts/pipelines/components/graph/utils.js
+45
-0
app/assets/javascripts/pipelines/pipeline_details_bundle.js
app/assets/javascripts/pipelines/pipeline_details_bundle.js
+3
-3
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
+305
-0
spec/frontend/pipelines/graph/graph_component_spec.js
spec/frontend/pipelines/graph/graph_component_spec.js
+25
-277
spec/frontend/pipelines/graph/mock_data.js
spec/frontend/pipelines/graph/mock_data.js
+487
-249
spec/frontend/pipelines/graph/mock_data_legacy.js
spec/frontend/pipelines/graph/mock_data_legacy.js
+261
-0
No files found.
app/assets/javascripts/pipelines/components/graph/graph_component.vue
View file @
d8646823
<
script
>
import
{
escape
,
capitalize
}
from
'
lodash
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
StageColumnComponent
from
'
./stage_column_component.vue
'
;
import
GraphWidthMixin
from
'
../../mixins/graph_width_mixin
'
;
import
LinkedPipelinesColumn
from
'
./linked_pipelines_column.vue
'
;
import
GraphBundleMixin
from
'
../../mixins/graph_pipeline_bundle_mixin
'
;
import
{
UPSTREAM
,
DOWNSTREAM
,
MAIN
}
from
'
./constants
'
;
import
{
MAIN
}
from
'
./constants
'
;
export
default
{
name
:
'
PipelineGraph
'
,
components
:
{
StageColumnComponent
,
GlLoadingIcon
,
LinkedPipelinesColumn
,
},
mixins
:
[
Graph
WidthMixin
,
Graph
BundleMixin
],
mixins
:
[
GraphBundleMixin
],
props
:
{
isLoading
:
{
type
:
Boolean
,
required
:
true
,
},
pipeline
:
{
type
:
Object
,
required
:
true
,
},
isLinkedPipeline
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
mediator
:
{
pipeline
:
{
type
:
Object
,
required
:
true
,
},
...
...
@@ -39,60 +26,9 @@ export default {
default
:
MAIN
,
},
},
upstream
:
UPSTREAM
,
downstream
:
DOWNSTREAM
,
data
()
{
return
{
downstreamMarginTop
:
null
,
jobName
:
null
,
pipelineExpanded
:
{
jobName
:
''
,
expanded
:
false
,
},
};
},
computed
:
{
graph
()
{
return
this
.
pipeline
.
details
?.
stages
;
},
hasUpstream
()
{
return
(
this
.
type
!==
this
.
$options
.
downstream
&&
this
.
upstreamPipelines
&&
this
.
pipeline
.
triggered_by
!==
null
);
},
upstreamPipelines
()
{
return
this
.
pipeline
.
triggered_by
;
},
hasDownstream
()
{
return
(
this
.
type
!==
this
.
$options
.
upstream
&&
this
.
downstreamPipelines
&&
this
.
pipeline
.
triggered
.
length
>
0
);
},
downstreamPipelines
()
{
return
this
.
pipeline
.
triggered
;
},
expandedUpstream
()
{
return
(
this
.
pipeline
.
triggered_by
&&
Array
.
isArray
(
this
.
pipeline
.
triggered_by
)
&&
this
.
pipeline
.
triggered_by
.
find
(
el
=>
el
.
isExpanded
)
);
},
expandedDownstream
()
{
return
this
.
pipeline
.
triggered
&&
this
.
pipeline
.
triggered
.
find
(
el
=>
el
.
isExpanded
);
},
pipelineTypeUpstream
()
{
return
this
.
type
!==
this
.
$options
.
downstream
&&
this
.
expandedUpstream
;
},
pipelineTypeDownstream
()
{
return
this
.
type
!==
this
.
$options
.
upstream
&&
this
.
expandedDownstream
;
},
pipelineProjectId
()
{
return
this
.
pipeline
.
project
.
id
;
return
this
.
pipeline
.
stages
;
},
},
methods
:
{
...
...
@@ -158,22 +94,6 @@ export default {
hasUpstreamColumn
(
index
)
{
return
index
===
0
&&
this
.
hasUpstream
;
},
setJob
(
jobName
)
{
this
.
jobName
=
jobName
;
},
setPipelineExpanded
(
jobName
,
expanded
)
{
if
(
expanded
)
{
this
.
pipelineExpanded
=
{
jobName
,
expanded
,
};
}
else
{
this
.
pipelineExpanded
=
{
expanded
,
jobName
:
''
,
};
}
},
},
};
</
script
>
...
...
@@ -183,48 +103,12 @@ export default {
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=
"lg"
/>
<pipeline-graph
v-if=
"pipelineTypeUpstream"
:type=
"$options.upstream"
class=
"d-inline-block upstream-pipeline"
:class=
"`js-upstream-pipeline-$
{expandedUpstream.id}`"
:is-loading="false"
:pipeline="expandedUpstream"
:is-linked-pipeline="true"
:mediator="mediator"
@onClickUpstreamPipeline="clickUpstreamPipeline"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
<linked-pipelines-column
v-if=
"hasUpstream"
:type=
"$options.upstream"
:linked-pipelines=
"upstreamPipelines"
:column-title=
"__('Upstream')"
:project-id=
"pipelineProjectId"
@
linkedPipelineClick=
"$emit('onClickUpstreamPipeline', $event)"
/>
<ul
v-if=
"!isLoading"
:class=
"
{
'inline js-has-linked-pipelines': hasDownstream || hasUpstream,
}"
class="stage-column-list align-top"
>
<div>
<ul
class=
"stage-column-list align-top"
>
<stage-column-component
v-for=
"(stage, index) in graph"
:key=
"stage.name"
:class=
"
{
'has-upstream gl-ml-11': hasUpstreamColumn(index),
'has-only-one-job': hasOnlyOneJob(stage),
'gl-mr-26': shouldAddRightMargin(index),
}"
...
...
@@ -232,38 +116,10 @@ export default {
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:has-upstream="hasUpstream"
:action="stage.status.action"
:job-hovered="jobName"
:pipeline-expanded="pipelineExpanded"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
<linked-pipelines-column
v-if=
"hasDownstream"
:type=
"$options.downstream"
:linked-pipelines=
"downstreamPipelines"
:column-title=
"__('Downstream')"
:project-id=
"pipelineProjectId"
@
linkedPipelineClick=
"handleClickedDownstream"
@
downstreamHovered=
"setJob"
@
pipelineExpandToggle=
"setPipelineExpanded"
/>
<pipeline-graph
v-if=
"pipelineTypeDownstream"
:type=
"$options.downstream"
class=
"d-inline-block"
:class=
"`js-downstream-pipeline-$
{expandedDownstream.id}`"
:is-loading="false"
:pipeline="expandedDownstream"
:is-linked-pipeline="true"
:style="{ 'margin-top': downstreamMarginTop }"
:mediator="mediator"
@onClickDownstreamPipeline="clickDownstreamPipeline"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
</div>
</div>
</div>
...
...
app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
0 → 100644
View file @
d8646823
<
script
>
import
{
escape
,
capitalize
}
from
'
lodash
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
StageColumnComponent
from
'
./stage_column_component.vue
'
;
import
GraphWidthMixin
from
'
../../mixins/graph_width_mixin
'
;
import
LinkedPipelinesColumn
from
'
./linked_pipelines_column.vue
'
;
import
GraphBundleMixin
from
'
../../mixins/graph_pipeline_bundle_mixin
'
;
import
{
UPSTREAM
,
DOWNSTREAM
,
MAIN
}
from
'
./constants
'
;
export
default
{
name
:
'
PipelineGraphLegacy
'
,
components
:
{
StageColumnComponent
,
GlLoadingIcon
,
LinkedPipelinesColumn
,
},
mixins
:
[
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
{
downstreamMarginTop
:
null
,
jobName
:
null
,
pipelineExpanded
:
{
jobName
:
''
,
expanded
:
false
,
},
};
},
computed
:
{
graph
()
{
return
this
.
pipeline
.
details
?.
stages
;
},
hasUpstream
()
{
return
(
this
.
type
!==
this
.
$options
.
downstream
&&
this
.
upstreamPipelines
&&
this
.
pipeline
.
triggered_by
!==
null
);
},
upstreamPipelines
()
{
return
this
.
pipeline
.
triggered_by
;
},
hasDownstream
()
{
return
(
this
.
type
!==
this
.
$options
.
upstream
&&
this
.
downstreamPipelines
&&
this
.
pipeline
.
triggered
.
length
>
0
);
},
downstreamPipelines
()
{
return
this
.
pipeline
.
triggered
;
},
expandedUpstream
()
{
return
(
this
.
pipeline
.
triggered_by
&&
Array
.
isArray
(
this
.
pipeline
.
triggered_by
)
&&
this
.
pipeline
.
triggered_by
.
find
(
el
=>
el
.
isExpanded
)
);
},
expandedDownstream
()
{
return
this
.
pipeline
.
triggered
&&
this
.
pipeline
.
triggered
.
find
(
el
=>
el
.
isExpanded
);
},
pipelineTypeUpstream
()
{
return
this
.
type
!==
this
.
$options
.
downstream
&&
this
.
expandedUpstream
;
},
pipelineTypeDownstream
()
{
return
this
.
type
!==
this
.
$options
.
upstream
&&
this
.
expandedDownstream
;
},
pipelineProjectId
()
{
return
this
.
pipeline
.
project
.
id
;
},
},
methods
:
{
capitalizeStageName
(
name
)
{
const
escapedName
=
escape
(
name
);
return
capitalize
(
escapedName
);
},
isFirstColumn
(
index
)
{
return
index
===
0
;
},
stageConnectorClass
(
index
,
stage
)
{
let
className
;
// If it's the first stage column and only has one job
if
(
this
.
isFirstColumn
(
index
)
&&
stage
.
groups
.
length
===
1
)
{
className
=
'
no-margin
'
;
}
else
if
(
index
>
0
)
{
// If it is not the first column
className
=
'
left-margin
'
;
}
return
className
;
},
refreshPipelineGraph
()
{
this
.
$emit
(
'
refreshPipelineGraph
'
);
},
/**
* CSS class is applied:
* - if pipeline graph contains only one stage column component
*
* @param {number} index
* @returns {boolean}
*/
shouldAddRightMargin
(
index
)
{
return
!
(
index
===
this
.
graph
.
length
-
1
);
},
handleClickedDownstream
(
pipeline
,
clickedIndex
,
downstreamNode
)
{
/**
* Calculates the margin top of the clicked downstream pipeline by
* subtracting the clicked downstream pipelines offsetTop by it's parent's
* offsetTop and then subtracting 15
*/
this
.
downstreamMarginTop
=
this
.
calculateMarginTop
(
downstreamNode
,
15
);
/**
* If the expanded trigger is defined and the id is different than the
* pipeline we clicked, then it means we clicked on a sibling downstream link
* and we want to reset the pipeline store. Triggering the reset without
* this condition would mean not allowing downstreams of downstreams to expand
*/
if
(
this
.
expandedDownstream
?.
id
!==
pipeline
.
id
)
{
this
.
$emit
(
'
onResetDownstream
'
,
this
.
pipeline
,
pipeline
);
}
this
.
$emit
(
'
onClickDownstreamPipeline
'
,
pipeline
);
},
calculateMarginTop
(
downstreamNode
,
pixelDiff
)
{
return
`
${
downstreamNode
.
offsetTop
-
downstreamNode
.
offsetParent
.
offsetTop
-
pixelDiff
}
px`
;
},
hasOnlyOneJob
(
stage
)
{
return
stage
.
groups
.
length
===
1
;
},
hasUpstreamColumn
(
index
)
{
return
index
===
0
&&
this
.
hasUpstream
;
},
setJob
(
jobName
)
{
this
.
jobName
=
jobName
;
},
setPipelineExpanded
(
jobName
,
expanded
)
{
if
(
expanded
)
{
this
.
pipelineExpanded
=
{
jobName
,
expanded
,
};
}
else
{
this
.
pipelineExpanded
=
{
expanded
,
jobName
:
''
,
};
}
},
},
};
</
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=
"lg"
/>
<pipeline-graph-legacy
v-if=
"pipelineTypeUpstream"
:type=
"$options.upstream"
class=
"d-inline-block upstream-pipeline"
:class=
"`js-upstream-pipeline-$
{expandedUpstream.id}`"
:is-loading="false"
:pipeline="expandedUpstream"
:is-linked-pipeline="true"
:mediator="mediator"
@onClickUpstreamPipeline="clickUpstreamPipeline"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
<linked-pipelines-column
v-if=
"hasUpstream"
:type=
"$options.upstream"
:linked-pipelines=
"upstreamPipelines"
:column-title=
"__('Upstream')"
:project-id=
"pipelineProjectId"
@
linkedPipelineClick=
"$emit('onClickUpstreamPipeline', $event)"
/>
<ul
v-if=
"!isLoading"
:class=
"
{
'inline js-has-linked-pipelines': hasDownstream || hasUpstream,
}"
class="stage-column-list align-top"
>
<stage-column-component
v-for=
"(stage, index) in graph"
:key=
"stage.name"
:class=
"
{
'has-upstream gl-ml-11': hasUpstreamColumn(index),
'has-only-one-job': hasOnlyOneJob(stage),
'gl-mr-26': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:has-upstream="hasUpstream"
:action="stage.status.action"
:job-hovered="jobName"
:pipeline-expanded="pipelineExpanded"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
<linked-pipelines-column
v-if=
"hasDownstream"
:type=
"$options.downstream"
:linked-pipelines=
"downstreamPipelines"
:column-title=
"__('Downstream')"
:project-id=
"pipelineProjectId"
@
linkedPipelineClick=
"handleClickedDownstream"
@
downstreamHovered=
"setJob"
@
pipelineExpandToggle=
"setPipelineExpanded"
/>
<pipeline-graph-legacy
v-if=
"pipelineTypeDownstream"
:type=
"$options.downstream"
class=
"d-inline-block"
:class=
"`js-downstream-pipeline-$
{expandedDownstream.id}`"
:is-loading="false"
:pipeline="expandedDownstream"
:is-linked-pipeline="true"
:style="{ 'margin-top': downstreamMarginTop }"
:mediator="mediator"
@onClickDownstreamPipeline="clickDownstreamPipeline"
@refreshPipelineGraph="requestRefreshPipelineGraph"
/>
</div>
</div>
</div>
</
template
>
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
View file @
d8646823
...
...
@@ -64,7 +64,7 @@ export default {
</
script
>
<
template
>
<li
:class=
"stageConnectorClass"
class=
"stage-column"
>
<div
class=
"stage-name position-relative"
>
<div
class=
"stage-name position-relative"
data-testid=
"stage-column-title"
>
{{
title
}}
<action-component
v-if=
"hasAction"
...
...
app/assets/javascripts/pipelines/components/graph/utils.js
0 → 100644
View file @
d8646823
const
unwrapPipelineData
=
(
mainPipelineId
,
data
)
=>
{
if
(
!
data
?.
project
?.
pipeline
)
{
return
null
;
}
const
{
id
,
upstream
,
downstream
,
stages
:
{
nodes
:
stages
},
}
=
data
.
project
.
pipeline
;
const
unwrappedNestedGroups
=
stages
.
map
(
stage
=>
{
const
{
groups
:
{
nodes
:
groups
},
}
=
stage
;
return
{
...
stage
,
groups
};
});
const
nodes
=
unwrappedNestedGroups
.
map
(({
name
,
status
,
groups
})
=>
{
const
groupsWithJobs
=
groups
.
map
(
group
=>
{
const
jobs
=
group
.
jobs
.
nodes
.
map
(
job
=>
{
const
{
needs
}
=
job
;
return
{
...
job
,
needs
:
needs
.
nodes
.
map
(
need
=>
need
.
name
)
};
});
return
{
...
group
,
jobs
};
});
return
{
name
,
status
,
groups
:
groupsWithJobs
};
});
const
addMulti
=
pipeline
=>
{
return
{
...
pipeline
,
multiproject
:
mainPipelineId
!==
pipeline
.
id
};
};
return
{
id
,
stages
:
nodes
,
upstream
:
upstream
?
[
upstream
].
map
(
addMulti
)
:
[],
downstream
:
downstream
?
downstream
.
map
(
addMulti
)
:
[],
};
};
export
{
unwrapPipelineData
};
app/assets/javascripts/pipelines/pipeline_details_bundle.js
View file @
d8646823
...
...
@@ -3,7 +3,7 @@ import { deprecatedCreateFlash as Flash } from '~/flash';
import
Translate
from
'
~/vue_shared/translate
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
setUrlFragment
,
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
pipelineGraph
from
'
./components/graph/graph_component
.vue
'
;
import
PipelineGraphLegacy
from
'
./components/graph/graph_component_legacy
.vue
'
;
import
createDagApp
from
'
./pipeline_details_dag
'
;
import
GraphBundleMixin
from
'
./mixins/graph_pipeline_bundle_mixin
'
;
import
legacyPipelineHeader
from
'
./components/legacy_header_component.vue
'
;
...
...
@@ -28,7 +28,7 @@ const createLegacyPipelinesDetailApp = mediator => {
new
Vue
({
el
:
SELECTORS
.
PIPELINE_GRAPH
,
components
:
{
pipelineGraph
,
PipelineGraphLegacy
,
},
mixins
:
[
GraphBundleMixin
],
data
()
{
...
...
@@ -37,7 +37,7 @@ const createLegacyPipelinesDetailApp = mediator => {
};
},
render
(
createElement
)
{
return
createElement
(
'
pipeline-graph
'
,
{
return
createElement
(
'
pipeline-graph
-legacy
'
,
{
props
:
{
isLoading
:
this
.
mediator
.
state
.
isLoading
,
pipeline
:
this
.
mediator
.
store
.
state
.
pipeline
,
...
...
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
0 → 100644
View file @
d8646823
import
Vue
from
'
vue
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
setHTMLFixture
}
from
'
helpers/fixtures
'
;
import
PipelineStore
from
'
~/pipelines/stores/pipeline_store
'
;
import
GraphComponentLegacy
from
'
~/pipelines/components/graph/graph_component_legacy.vue
'
;
import
StageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
import
linkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
graphJSON
from
'
./mock_data_legacy
'
;
import
linkedPipelineJSON
from
'
./linked_pipelines_mock_data
'
;
import
PipelinesMediator
from
'
~/pipelines/pipeline_details_mediator
'
;
describe
(
'
graph component
'
,
()
=>
{
let
store
;
let
mediator
;
let
wrapper
;
const
findExpandPipelineBtn
=
()
=>
wrapper
.
find
(
'
[data-testid="expandPipelineButton"]
'
);
const
findAllExpandPipelineBtns
=
()
=>
wrapper
.
findAll
(
'
[data-testid="expandPipelineButton"]
'
);
const
findStageColumns
=
()
=>
wrapper
.
findAll
(
StageColumnComponent
);
const
findStageColumnAt
=
i
=>
findStageColumns
().
at
(
i
);
beforeEach
(()
=>
{
mediator
=
new
PipelinesMediator
({
endpoint
:
''
});
store
=
new
PipelineStore
();
store
.
storePipeline
(
linkedPipelineJSON
);
setHTMLFixture
(
'
<div class="layout-page"></div>
'
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
while is loading
'
,
()
=>
{
it
(
'
should render a loading icon
'
,
()
=>
{
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
true
,
pipeline
:
{},
mediator
,
},
});
expect
(
wrapper
.
find
(
'
.gl-spinner
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
with data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
graphJSON
,
mediator
,
},
});
});
it
(
'
renders the graph
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-pipeline-graph
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.loading-icon
'
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
'
.stage-column-list
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders columns in the graph
'
,
()
=>
{
expect
(
findStageColumns
()).
toHaveLength
(
graphJSON
.
details
.
stages
.
length
);
});
});
describe
(
'
when linked pipelines are present
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
});
describe
(
'
rendered output
'
,
()
=>
{
it
(
'
should include the pipelines graph
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-pipeline-graph
'
).
exists
()).
toBe
(
true
);
});
it
(
'
should not include the loading icon
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.fa-spinner
'
).
exists
()).
toBe
(
false
);
});
it
(
'
should include the stage column
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
exists
()).
toBe
(
true
);
});
it
(
'
stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
classes
()).
toEqual
(
expect
.
arrayContaining
([
'
no-margin
'
,
'
gl-mr-26
'
,
'
has-only-one-job
'
]),
);
});
it
(
'
should include the left-margin class on the second child
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
it
(
'
should include the left-connector class in the build of the second child
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
)
.
find
(
'
.build:nth-child(1)
'
)
.
classes
(
'
left-connector
'
),
).
toBe
(
true
);
});
it
(
'
should include the js-has-linked-pipelines flag
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-has-linked-pipelines
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
computeds and methods
'
,
()
=>
{
describe
(
'
capitalizeStageName
'
,
()
=>
{
it
(
'
it capitalizes the stage name
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.stage-column .stage-name
'
)
.
at
(
1
)
.
text
(),
).
toBe
(
'
Prebuild
'
);
});
});
describe
(
'
stageConnectorClass
'
,
()
=>
{
it
(
'
it returns left-margin when there is a triggerer
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
});
});
describe
(
'
linked pipelines components
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
});
it
(
'
should render an upstream pipelines column at first position
'
,
()
=>
{
expect
(
wrapper
.
find
(
linkedPipelinesColumn
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.stage-column .stage-name
'
).
text
()).
toBe
(
'
Upstream
'
);
});
it
(
'
should render a downstream pipelines column at last position
'
,
()
=>
{
const
stageColumnNames
=
wrapper
.
findAll
(
'
.stage-column .stage-name
'
);
expect
(
wrapper
.
find
(
linkedPipelinesColumn
).
exists
()).
toBe
(
true
);
expect
(
stageColumnNames
.
at
(
stageColumnNames
.
length
-
1
).
text
()).
toBe
(
'
Downstream
'
);
});
describe
(
'
triggered by
'
,
()
=>
{
describe
(
'
on click
'
,
()
=>
{
it
(
'
should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked
'
,
()
=>
{
const
btnWrapper
=
findExpandPipelineBtn
();
btnWrapper
.
trigger
(
'
click
'
);
btnWrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
emitted
().
onClickUpstreamPipeline
).
toEqual
([
store
.
state
.
pipeline
.
triggered_by
,
]);
});
});
});
describe
(
'
with expanded pipeline
'
,
()
=>
{
it
(
'
should render expanded pipeline
'
,
done
=>
{
// expand the pipeline
store
.
state
.
pipeline
.
triggered_by
[
0
].
isExpanded
=
true
;
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
wrapper
.
find
(
'
.js-upstream-pipeline-12
'
).
exists
()).
toBe
(
true
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
describe
(
'
triggered
'
,
()
=>
{
describe
(
'
on click
'
,
()
=>
{
it
(
'
should emit `onClickTriggered`
'
,
()
=>
{
// We have to mock this method since we do both style change and
// emit and event, not mocking returns an error.
wrapper
.
setMethods
({
handleClickedDownstream
:
jest
.
fn
(()
=>
wrapper
.
vm
.
$emit
(
'
onClickTriggered
'
,
...
store
.
state
.
pipeline
.
triggered
),
),
});
const
btnWrappers
=
findAllExpandPipelineBtns
();
const
downstreamBtnWrapper
=
btnWrappers
.
at
(
btnWrappers
.
length
-
1
);
downstreamBtnWrapper
.
trigger
(
'
click
'
);
downstreamBtnWrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
emitted
().
onClickTriggered
).
toEqual
([
store
.
state
.
pipeline
.
triggered
]);
});
});
});
describe
(
'
with expanded pipeline
'
,
()
=>
{
it
(
'
should render expanded pipeline
'
,
done
=>
{
// expand the pipeline
store
.
state
.
pipeline
.
triggered
[
0
].
isExpanded
=
true
;
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
wrapper
.
find
(
'
.js-downstream-pipeline-34993051
'
)).
not
.
toBeNull
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
describe
(
'
when column requests a refresh
'
,
()
=>
{
beforeEach
(()
=>
{
findStageColumnAt
(
0
).
vm
.
$emit
(
'
refreshPipelineGraph
'
);
});
it
(
'
refreshPipelineGraph is emitted
'
,
()
=>
{
expect
(
wrapper
.
emitted
().
refreshPipelineGraph
).
toHaveLength
(
1
);
});
});
});
});
});
describe
(
'
when linked pipelines are not present
'
,
()
=>
{
beforeEach
(()
=>
{
const
pipeline
=
Object
.
assign
(
linkedPipelineJSON
,
{
triggered
:
null
,
triggered_by
:
null
});
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
,
mediator
,
},
});
});
describe
(
'
rendered output
'
,
()
=>
{
it
(
'
should include the first column with a no margin
'
,
()
=>
{
const
firstColumn
=
wrapper
.
find
(
'
.stage-column
'
);
expect
(
firstColumn
.
classes
(
'
no-margin
'
)).
toBe
(
true
);
});
it
(
'
should not render a linked pipelines column
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.linked-pipelines-column
'
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
stageConnectorClass
'
,
()
=>
{
it
(
'
it returns no-margin when no triggerer and there is one job
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
classes
(
'
no-margin
'
)).
toBe
(
true
);
});
it
(
'
it returns left-margin when no triggerer and not the first stage
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
});
});
describe
(
'
capitalizeStageName
'
,
()
=>
{
it
(
'
capitalizes and escapes stage name
'
,
()
=>
{
wrapper
=
mount
(
GraphComponentLegacy
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
graphJSON
,
mediator
,
},
});
expect
(
findStageColumnAt
(
1
).
props
(
'
title
'
)).
toEqual
(
'
Deploy <img src=x onerror=alert(document.domain)>
'
,
);
});
});
});
spec/frontend/pipelines/graph/graph_component_spec.js
View file @
d8646823
import
Vue
from
'
vue
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
setHTMLFixture
}
from
'
helpers/fixtures
'
;
import
PipelineStore
from
'
~/pipelines/stores/pipeline_store
'
;
import
graphComponent
from
'
~/pipelines/components/graph/graph_component.vue
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
PipelineGraph
from
'
~/pipelines/components/graph/graph_component.vue
'
;
import
StageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
import
linkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
graphJSON
from
'
./mock_data
'
;
import
linkedPipelineJSON
from
'
./linked_pipelines_mock_data
'
;
import
PipelinesMediator
from
'
~/pipelines/pipeline_details_mediator
'
;
import
LinkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
{
unwrapPipelineData
}
from
'
~/pipelines/components/graph/utils
'
;
import
{
mockPipelineResponse
}
from
'
./mock_data
'
;
describe
(
'
graph component
'
,
()
=>
{
let
store
;
let
mediator
;
let
wrapper
;
const
findExpandPipelineBtn
=
()
=>
wrapper
.
find
(
'
[data-testid="expandPipelineButton"]
'
);
const
findAllExpandPipelineBtns
=
()
=>
wrapper
.
findAll
(
'
[data-testid="expandPipelineButton"]
'
);
const
findLinkedColumns
=
()
=>
wrapper
.
findAll
(
LinkedPipelinesColumn
);
const
findStageColumns
=
()
=>
wrapper
.
findAll
(
StageColumnComponent
);
const
findStageColumnAt
=
i
=>
findStageColumns
().
at
(
i
);
beforeEach
(()
=>
{
mediator
=
new
PipelinesMediator
({
endpoint
:
''
});
store
=
new
PipelineStore
();
store
.
storePipeline
(
linkedPipelineJSON
);
setHTMLFixture
(
'
<div class="layout-page"></div>
'
);
});
const
generateResponse
=
raw
=>
unwrapPipelineData
(
raw
.
data
.
project
.
pipeline
.
id
,
raw
.
data
);
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
const
defaultProps
=
{
pipeline
:
generateResponse
(
mockPipelineResponse
),
};
describe
(
'
while is loading
'
,
()
=>
{
it
(
'
should render a loading icon
'
,
()
=>
{
wrapper
=
mount
(
graphComponent
,
{
const
createComponent
=
({
mountFn
=
shallowMount
,
props
=
{}
}
=
{})
=>
{
wrapper
=
mountFn
(
PipelineGraph
,
{
propsData
:
{
isLoading
:
true
,
pipeline
:
{},
mediator
,
...
defaultProps
,
...
props
,
},
});
};
expect
(
wrapper
.
find
(
'
.gl-spinner
'
).
exists
()).
toBe
(
true
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
with data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
graphJSON
,
mediator
,
},
});
});
it
(
'
renders the graph
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-pipeline-graph
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.loading-icon
'
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
'
.stage-column-list
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders columns in the graph
'
,
()
=>
{
expect
(
findStageColumns
()).
toHaveLength
(
graphJSON
.
details
.
stages
.
length
);
});
});
describe
(
'
when linked pipelines are present
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
});
describe
(
'
rendered output
'
,
()
=>
{
it
(
'
should include the pipelines graph
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-pipeline-graph
'
).
exists
()).
toBe
(
true
);
});
it
(
'
should not include the loading icon
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.fa-spinner
'
).
exists
()).
toBe
(
false
);
});
it
(
'
should include the stage column
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
exists
()).
toBe
(
true
);
});
it
(
'
stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
classes
()).
toEqual
(
expect
.
arrayContaining
([
'
no-margin
'
,
'
gl-mr-26
'
,
'
has-only-one-job
'
]),
);
});
it
(
'
should include the left-margin class on the second child
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
it
(
'
should include the left-connector class in the build of the second child
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
)
.
find
(
'
.build:nth-child(1)
'
)
.
classes
(
'
left-connector
'
),
).
toBe
(
true
);
});
it
(
'
should include the js-has-linked-pipelines flag
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-has-linked-pipelines
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
computeds and methods
'
,
()
=>
{
describe
(
'
capitalizeStageName
'
,
()
=>
{
it
(
'
it capitalizes the stage name
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.stage-column .stage-name
'
)
.
at
(
1
)
.
text
(),
).
toBe
(
'
Prebuild
'
);
});
});
describe
(
'
stageConnectorClass
'
,
()
=>
{
it
(
'
it returns left-margin when there is a triggerer
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
});
});
describe
(
'
linked pipelines components
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
});
it
(
'
should render an upstream pipelines column at first position
'
,
()
=>
{
expect
(
wrapper
.
find
(
linkedPipelinesColumn
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.stage-column .stage-name
'
).
text
()).
toBe
(
'
Upstream
'
);
});
it
(
'
should render a downstream pipelines column at last position
'
,
()
=>
{
const
stageColumnNames
=
wrapper
.
findAll
(
'
.stage-column .stage-name
'
);
expect
(
wrapper
.
find
(
linkedPipelinesColumn
).
exists
()).
toBe
(
true
);
expect
(
stageColumnNames
.
at
(
stageColumnNames
.
length
-
1
).
text
()).
toBe
(
'
Downstream
'
);
});
describe
(
'
triggered by
'
,
()
=>
{
describe
(
'
on click
'
,
()
=>
{
it
(
'
should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked
'
,
()
=>
{
const
btnWrapper
=
findExpandPipelineBtn
();
btnWrapper
.
trigger
(
'
click
'
);
btnWrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
emitted
().
onClickUpstreamPipeline
).
toEqual
([
store
.
state
.
pipeline
.
triggered_by
,
]);
});
});
});
describe
(
'
with expanded pipeline
'
,
()
=>
{
it
(
'
should render expanded pipeline
'
,
done
=>
{
// expand the pipeline
store
.
state
.
pipeline
.
triggered_by
[
0
].
isExpanded
=
true
;
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
wrapper
.
find
(
'
.js-upstream-pipeline-12
'
).
exists
()).
toBe
(
true
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
describe
(
'
triggered
'
,
()
=>
{
describe
(
'
on click
'
,
()
=>
{
it
(
'
should emit `onClickTriggered`
'
,
()
=>
{
// We have to mock this method since we do both style change and
// emit and event, not mocking returns an error.
wrapper
.
setMethods
({
handleClickedDownstream
:
jest
.
fn
(()
=>
wrapper
.
vm
.
$emit
(
'
onClickTriggered
'
,
...
store
.
state
.
pipeline
.
triggered
),
),
});
const
btnWrappers
=
findAllExpandPipelineBtns
();
const
downstreamBtnWrapper
=
btnWrappers
.
at
(
btnWrappers
.
length
-
1
);
downstreamBtnWrapper
.
trigger
(
'
click
'
);
downstreamBtnWrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
emitted
().
onClickTriggered
).
toEqual
([
store
.
state
.
pipeline
.
triggered
]);
});
});
});
describe
(
'
with expanded pipeline
'
,
()
=>
{
it
(
'
should render expanded pipeline
'
,
done
=>
{
// expand the pipeline
store
.
state
.
pipeline
.
triggered
[
0
].
isExpanded
=
true
;
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
store
.
state
.
pipeline
,
mediator
,
},
});
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
wrapper
.
find
(
'
.js-downstream-pipeline-34993051
'
)).
not
.
toBeNull
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
describe
(
'
when column requests a refresh
'
,
()
=>
{
beforeEach
(()
=>
{
findStageColumnAt
(
0
).
vm
.
$emit
(
'
refreshPipelineGraph
'
);
createComponent
();
});
it
(
'
refreshPipelineGraph is emitted
'
,
()
=>
{
expect
(
wrapper
.
emitted
().
refreshPipelineGraph
).
toHaveLength
(
1
);
});
});
});
it
(
'
renders the main columns in the graph
'
,
()
=>
{
expect
(
findStageColumns
()).
toHaveLength
(
defaultProps
.
pipeline
.
stages
.
length
);
});
});
describe
(
'
when linked pipelines are not present
'
,
()
=>
{
beforeEach
(()
=>
{
const
pipeline
=
Object
.
assign
(
linkedPipelineJSON
,
{
triggered
:
null
,
triggered_by
:
null
});
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
,
mediator
,
},
});
});
describe
(
'
rendered output
'
,
()
=>
{
it
(
'
should include the first column with a no margin
'
,
()
=>
{
const
firstColumn
=
wrapper
.
find
(
'
.stage-column
'
);
expect
(
firstColumn
.
classes
(
'
no-margin
'
)).
toBe
(
true
);
createComponent
();
});
it
(
'
should not render a linked pipelines column
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.linked-pipelines-column
'
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
stageConnectorClass
'
,
()
=>
{
it
(
'
it returns no-margin when no triggerer and there is one job
'
,
()
=>
{
expect
(
findStageColumnAt
(
0
).
classes
(
'
no-margin
'
)).
toBe
(
true
);
});
it
(
'
it returns left-margin when no triggerer and not the first stage
'
,
()
=>
{
expect
(
findStageColumnAt
(
1
).
classes
(
'
left-margin
'
)).
toBe
(
true
);
});
});
});
describe
(
'
capitalizeStageName
'
,
()
=>
{
it
(
'
capitalizes and escapes stage name
'
,
()
=>
{
wrapper
=
mount
(
graphComponent
,
{
propsData
:
{
isLoading
:
false
,
pipeline
:
graphJSON
,
mediator
,
},
});
expect
(
findStageColumnAt
(
1
).
props
(
'
title
'
)).
toEqual
(
'
Deploy <img src=x onerror=alert(document.domain)>
'
,
);
expect
(
findLinkedColumns
()).
toHaveLength
(
0
);
});
});
});
spec/frontend/pipelines/graph/mock_data.js
View file @
d8646823
export
default
{
id
:
123
,
user
:
{
name
:
'
Root
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
null
,
web_url
:
'
http://localhost:3000/root
'
,
},
active
:
false
,
coverage
:
null
,
path
:
'
/root/ci-mock/pipelines/123
'
,
details
:
{
export
const
mockPipelineResponse
=
{
data
:
{
project
:
{
__typename
:
'
Project
'
,
pipeline
:
{
__typename
:
'
Pipeline
'
,
id
:
'
22
'
,
stages
:
{
__typename
:
'
CiStageConnection
'
,
nodes
:
[
{
__typename
:
'
CiStage
'
,
name
:
'
build
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
__typename
:
'
DetailedStatus
'
,
action
:
null
,
},
groups
:
{
__typename
:
'
CiGroupConnection
'
,
nodes
:
[
{
__typename
:
'
CiGroup
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
size
:
1
,
status
:
{
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
icon
:
'
status_success
'
,
},
duration
:
9
,
finished_at
:
'
2017-04-19T14:30:27.542Z
'
,
stag
es
:
[
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nod
es
:
[
{
name
:
'
test
'
,
title
:
'
test: passed
'
,
groups
:
[
__typename
:
'
CiJob
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1482
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1482/retry
'
,
title
:
'
Retry
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
},
],
},
},
{
name
:
'
test
'
,
__typename
:
'
CiGroup
'
,
name
:
'
build_b
'
,
size
:
1
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4153
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
icon
:
'
status_success
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1515
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1515/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4153/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
},
],
},
},
{
id
:
4153
,
name
:
'
test
'
,
build_path
:
'
/root/ci-mock/builds/4153
'
,
retry_path
:
'
/root/ci-mock/builds/4153/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-13T09:25:18.959Z
'
,
updated_at
:
'
2017-04-13T09:25:23.118Z
'
,
__typename
:
'
CiGroup
'
,
name
:
'
build_c
'
,
size
:
1
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4153
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
icon
:
'
status_success
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_c
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1484
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1484/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4153/retry
'
,
method
:
'
post
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
],
},
],
},
},
{
__typename
:
'
CiGroup
'
,
name
:
'
build_d
'
,
size
:
3
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123#test
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
},
path
:
'
/root/ci-mock/pipelines/123#test
'
,
dropdown_path
:
'
/root/ci-mock/pipelines/123/stage.json?stage=test
'
,
icon
:
'
status_success
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
name
:
'
deploy <img src=x onerror=alert(document.domain)>
'
,
title
:
'
deploy: passed
'
,
groups
:
[
__typename
:
'
CiJob
'
,
name
:
'
build_d 1/3
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1485
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1485/retry
'
,
title
:
'
Retry
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
},
{
name
:
'
deploy to production
'
,
size
:
1
,
__typename
:
'
CiJob
'
,
name
:
'
build_d 2/3
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1486
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4166
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1486/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4166/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
},
{
id
:
4166
,
name
:
'
deploy to production
'
,
build_path
:
'
/root/ci-mock/builds/4166
'
,
retry_path
:
'
/root/ci-mock/builds/4166/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-19T14:29:46.463Z
'
,
updated_at
:
'
2017-04-19T14:30:27.498Z
'
,
__typename
:
'
CiJob
'
,
name
:
'
build_d 3/3
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1487
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4166
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1487/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4166/retry
'
,
method
:
'
post
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[],
},
},
],
},
},
],
},
},
{
name
:
'
deploy to staging
'
,
__typename
:
'
CiStage
'
,
name
:
'
test
'
,
status
:
{
__typename
:
'
DetailedStatus
'
,
action
:
null
,
},
groups
:
{
__typename
:
'
CiGroupConnection
'
,
nodes
:
[
{
__typename
:
'
CiGroup
'
,
name
:
'
test_a
'
,
size
:
1
,
status
:
{
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
icon
:
'
status_success
'
,
text
:
'
passed
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
test_a
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1514
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1514/retry
'
,
title
:
'
Retry
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_c
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
},
],
},
},
],
},
},
{
__typename
:
'
CiGroup
'
,
name
:
'
test_b
'
,
size
:
2
,
status
:
{
__typename
:
'
DetailedStatus
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4159
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
icon
:
'
status_success
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
test_b 1/2
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1489
'
,
group
:
'
success
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1489/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4159/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_d 3/3
'
,
},
{
id
:
4159
,
name
:
'
deploy to staging
'
,
build_path
:
'
/root/ci-mock/builds/4159
'
,
retry_path
:
'
/root/ci-mock/builds/4159/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-18T16:32:08.420Z
'
,
updated_at
:
'
2017-04-18T16:32:12.631Z
'
,
__typename
:
'
CiJob
'
,
name
:
'
build_d 2/3
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_d 1/3
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
},
],
},
},
{
__typename
:
'
CiJob
'
,
name
:
'
test_b 2/2
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
tooltip
:
'
passed
'
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/jobs/1490
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4159
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
__typename
:
'
StatusAction
'
,
buttonTitle
:
'
Retry this job
'
,
icon
:
'
retry
'
,
path
:
'
/root/abcd-dag/-/jobs/1490/retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4159/retry
'
,
method
:
'
post
'
,
},
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_d 3/3
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_d 2/3
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_d 1/3
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
},
],
},
},
],
},
},
{
__typename
:
'
CiGroup
'
,
name
:
'
test_c
'
,
size
:
1
,
status
:
{
__typename
:
'
DetailedStatus
'
,
label
:
null
,
group
:
'
success
'
,
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
test_c
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
null
,
hasDetails
:
true
,
detailsPath
:
'
/root/kinder-pipe/-/pipelines/154
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123#deploy
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
null
,
},
path
:
'
/root/ci-mock/pipelines/123#deploy
'
,
dropdown_path
:
'
/root/ci-mock/pipelines/123/stage.json?stage=deploy
'
,
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_c
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
},
{
__typename
:
'
CiJob
'
,
name
:
'
build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl
'
,
},
],
artifacts
:
[],
manual_actions
:
[
},
},
],
},
},
{
name
:
'
deploy to production
'
,
path
:
'
/root/ci-mock/builds/4166/play
'
,
playable
:
false
,
__typename
:
'
CiGroup
'
,
name
:
'
test_d
'
,
size
:
1
,
status
:
{
__typename
:
'
DetailedStatus
'
,
label
:
null
,
group
:
'
success
'
,
icon
:
'
status_success
'
,
},
jobs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
test_d
'
,
scheduledAt
:
null
,
status
:
{
__typename
:
'
DetailedStatus
'
,
icon
:
'
status_success
'
,
tooltip
:
null
,
hasDetails
:
true
,
detailsPath
:
'
/root/abcd-dag/-/pipelines/153
'
,
group
:
'
success
'
,
action
:
null
,
},
needs
:
{
__typename
:
'
CiJobConnection
'
,
nodes
:
[
{
__typename
:
'
CiJob
'
,
name
:
'
build_b
'
,
},
],
},
flags
:
{
latest
:
true
,
triggered
:
false
,
stuck
:
false
,
yaml_errors
:
false
,
retryable
:
false
,
cancelable
:
false
,
},
ref
:
{
name
:
'
master
'
,
path
:
'
/root/ci-mock/tree/master
'
,
tag
:
false
,
branch
:
true
,
},
commit
:
{
id
:
'
798e5f902592192afaba73f4668ae30e56eae492
'
,
short_id
:
'
798e5f90
'
,
title
:
"
Merge branch 'new-branch' into 'master'
\r
"
,
created_at
:
'
2017-04-13T10:25:17.000+01:00
'
,
parent_ids
:
[
'
54d483b1ed156fbbf618886ddf7ab023e24f8738
'
,
'
c8e2d38a6c538822e81c57022a6e3a0cfedebbcc
'
,
},
],
message
:
"
Merge branch 'new-branch' into 'master'
\r\n\r\n
Add new file
\r\n\r\n
See merge request !1
"
,
author_name
:
'
Root
'
,
author_email
:
'
admin@example.com
'
,
authored_date
:
'
2017-04-13T10:25:17.000+01:00
'
,
committer_name
:
'
Root
'
,
committer_email
:
'
admin@example.com
'
,
committed_date
:
'
2017-04-13T10:25:17.000+01:00
'
,
author
:
{
name
:
'
Root
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
null
,
web_url
:
'
http://localhost:3000/root
'
,
},
author_gravatar_url
:
null
,
commit_url
:
'
http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492
'
,
commit_path
:
'
/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492
'
,
},
created_at
:
'
2017-04-13T09:25:18.881Z
'
,
updated_at
:
'
2017-04-19T14:30:27.561Z
'
,
},
},
],
},
},
],
},
},
},
},
};
spec/frontend/pipelines/graph/mock_data_legacy.js
0 → 100644
View file @
d8646823
export
default
{
id
:
123
,
user
:
{
name
:
'
Root
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
null
,
web_url
:
'
http://localhost:3000/root
'
,
},
active
:
false
,
coverage
:
null
,
path
:
'
/root/ci-mock/pipelines/123
'
,
details
:
{
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
},
duration
:
9
,
finished_at
:
'
2017-04-19T14:30:27.542Z
'
,
stages
:
[
{
name
:
'
test
'
,
title
:
'
test: passed
'
,
groups
:
[
{
name
:
'
test
'
,
size
:
1
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4153
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4153/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
{
id
:
4153
,
name
:
'
test
'
,
build_path
:
'
/root/ci-mock/builds/4153
'
,
retry_path
:
'
/root/ci-mock/builds/4153/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-13T09:25:18.959Z
'
,
updated_at
:
'
2017-04-13T09:25:23.118Z
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4153
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4153/retry
'
,
method
:
'
post
'
,
},
},
},
],
},
],
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123#test
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
},
path
:
'
/root/ci-mock/pipelines/123#test
'
,
dropdown_path
:
'
/root/ci-mock/pipelines/123/stage.json?stage=test
'
,
},
{
name
:
'
deploy <img src=x onerror=alert(document.domain)>
'
,
title
:
'
deploy: passed
'
,
groups
:
[
{
name
:
'
deploy to production
'
,
size
:
1
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4166
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4166/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
{
id
:
4166
,
name
:
'
deploy to production
'
,
build_path
:
'
/root/ci-mock/builds/4166
'
,
retry_path
:
'
/root/ci-mock/builds/4166/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-19T14:29:46.463Z
'
,
updated_at
:
'
2017-04-19T14:30:27.498Z
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4166
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4166/retry
'
,
method
:
'
post
'
,
},
},
},
],
},
{
name
:
'
deploy to staging
'
,
size
:
1
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4159
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4159/retry
'
,
method
:
'
post
'
,
},
},
jobs
:
[
{
id
:
4159
,
name
:
'
deploy to staging
'
,
build_path
:
'
/root/ci-mock/builds/4159
'
,
retry_path
:
'
/root/ci-mock/builds/4159/retry
'
,
playable
:
false
,
created_at
:
'
2017-04-18T16:32:08.420Z
'
,
updated_at
:
'
2017-04-18T16:32:12.631Z
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/builds/4159
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4159/retry
'
,
method
:
'
post
'
,
},
},
},
],
},
],
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
has_details
:
true
,
details_path
:
'
/root/ci-mock/pipelines/123#deploy
'
,
favicon
:
'
/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png
'
,
},
path
:
'
/root/ci-mock/pipelines/123#deploy
'
,
dropdown_path
:
'
/root/ci-mock/pipelines/123/stage.json?stage=deploy
'
,
},
],
artifacts
:
[],
manual_actions
:
[
{
name
:
'
deploy to production
'
,
path
:
'
/root/ci-mock/builds/4166/play
'
,
playable
:
false
,
},
],
},
flags
:
{
latest
:
true
,
triggered
:
false
,
stuck
:
false
,
yaml_errors
:
false
,
retryable
:
false
,
cancelable
:
false
,
},
ref
:
{
name
:
'
master
'
,
path
:
'
/root/ci-mock/tree/master
'
,
tag
:
false
,
branch
:
true
,
},
commit
:
{
id
:
'
798e5f902592192afaba73f4668ae30e56eae492
'
,
short_id
:
'
798e5f90
'
,
title
:
"
Merge branch 'new-branch' into 'master'
\r
"
,
created_at
:
'
2017-04-13T10:25:17.000+01:00
'
,
parent_ids
:
[
'
54d483b1ed156fbbf618886ddf7ab023e24f8738
'
,
'
c8e2d38a6c538822e81c57022a6e3a0cfedebbcc
'
,
],
message
:
"
Merge branch 'new-branch' into 'master'
\r\n\r\n
Add new file
\r\n\r\n
See merge request !1
"
,
author_name
:
'
Root
'
,
author_email
:
'
admin@example.com
'
,
authored_date
:
'
2017-04-13T10:25:17.000+01:00
'
,
committer_name
:
'
Root
'
,
committer_email
:
'
admin@example.com
'
,
committed_date
:
'
2017-04-13T10:25:17.000+01:00
'
,
author
:
{
name
:
'
Root
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
null
,
web_url
:
'
http://localhost:3000/root
'
,
},
author_gravatar_url
:
null
,
commit_url
:
'
http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492
'
,
commit_path
:
'
/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492
'
,
},
created_at
:
'
2017-04-13T09:25:18.881Z
'
,
updated_at
:
'
2017-04-19T14:30:27.561Z
'
,
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment