summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Schatz <jschatz@gitlab.com>2016-08-17 22:42:58 +0000
committerRuben Davila <rdavila84@gmail.com>2016-08-18 10:39:31 -0500
commit2673031677bbc776d82886a1e4168f4a27f2ee39 (patch)
tree9e056ba80b4ad371f1095c7ce4ed9eae70825f63
parent35e32d1566dfa0ff2727c4af727806571cfabb7b (diff)
downloadgitlab-ce-2673031677bbc776d82886a1e4168f4a27f2ee39.tar.gz
Merge branch '18141-pipeline-graph' into 'master'
Add pipeline graph ## What does this MR do? Adds pipeline visualization ## What are the relevant issue numbers? Closes #18141 Part of #19982 ## Screenshots (if relevant) ![Screen_Shot_2016-08-16_at_7.59.52_PM](/uploads/c9dd695d2ddbd2a85e98a5b4e500d52c/Screen_Shot_2016-08-16_at_7.59.52_PM.png) ![Screen_Shot_2016-08-16_at_7.55.49_PM](/uploads/5ab548cc5fc8a42371d3b54108798c02/Screen_Shot_2016-08-16_at_7.55.49_PM.png) See merge request !5742
-rw-r--r--CHANGELOG1
-rw-r--r--app/assets/javascripts/pipeline.js.es615
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss181
-rw-r--r--app/helpers/ci_status_helper.rb26
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/views/projects/ci/builds/_build_pipeline.html.haml14
-rw-r--r--app/views/projects/commit/_pipeline.html.haml22
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml9
-rw-r--r--db/fixtures/development/14_builds.rb9
-rw-r--r--spec/features/projects/pipelines_spec.rb6
11 files changed, 272 insertions, 17 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ba1087a4d75..46a060b6c88 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -113,6 +113,7 @@ v 8.11.0 (unreleased)
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
- Adds support for pending invitation project members importing projects
+ - Add pipeline visualization/graph on pipeline page
- Update devise initializer to turn on changed password notification emails. !5648 (tombell)
- Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
- Fix importing GitLab projects with an invalid MR source project
diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6
new file mode 100644
index 00000000000..bf33eb10100
--- /dev/null
+++ b/app/assets/javascripts/pipeline.js.es6
@@ -0,0 +1,15 @@
+(function() {
+ function toggleGraph() {
+ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
+ const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
+ const $btnText = $(this).find('.toggle-btn-text');
+
+ $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
+
+ const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
+
+ graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
+ }
+
+ $(document).on('click', '.toggle-pipeline-btn', toggleGraph);
+})();
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 50ac4d8449b..ce1c424624f 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -230,6 +230,187 @@
}
}
+// Pipeline visualization
+
+.toggle-pipeline-btn {
+ background-color: $gray-dark;
+
+ .caret {
+ border-top: none;
+ border-bottom: 4px solid;
+ }
+
+ &.graph-collapsed {
+ background-color: $white-light;
+
+ .caret {
+ border-bottom: none;
+ border-top: 4px solid;
+ }
+ }
+}
+
+.pipeline-graph {
+ width: 100%;
+ overflow: auto;
+ white-space: nowrap;
+ max-height: 500px;
+ transition: max-height 0.3s, padding 0.3s;
+
+ &.graph-collapsed {
+ max-height: 0;
+ padding: 0 16px;
+ }
+}
+
+.pipeline-visualization {
+ position: relative;
+ min-width: 1220px;
+
+ ul {
+ padding: 0;
+ }
+}
+
+.stage-column {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: 50px;
+
+ li {
+ list-style: none;
+ }
+
+ .stage-name {
+ margin-bottom: 15px;
+ font-weight: bold;
+ width: 150px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .build {
+ border: 1px solid $border-color;
+ position: relative;
+ padding: 6px 10px;
+ border-radius: 30px;
+ width: 150px;
+ margin-bottom: 10px;
+
+ &.playable {
+ background-color: $gray-light;
+ }
+
+ .build-content {
+ width: 130px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ a {
+ color: $layout-link-gray;
+ }
+ }
+
+ svg {
+ position: relative;
+ top: 2px;
+ margin-right: 5px;
+ }
+
+ .fa {
+ font-size: 13px;
+ }
+
+ // Connect first build in each stage with right horizontal line
+ &:first-child {
+ &::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: -54px;
+ border-top: 2px solid $border-color;
+ width: 54px;
+ height: 1px;
+ }
+ }
+
+ // Connect each build (except for first) with curved lines
+ &:not(:first-child) {
+ &::after, &::before {
+ content: '';
+ top: -47px;
+ position: absolute;
+ border-bottom: 2px solid $border-color;
+ width: 20px;
+ height: 65px;
+ }
+
+ // Right connecting curves
+ &::after {
+ right: -20px;
+ border-right: 2px solid $border-color;
+ border-radius: 0 0 50px;
+ }
+
+ // Left connecting curves
+ &::before {
+ left: -20px;
+ border-left: 2px solid $border-color;
+ border-radius: 0 0 0 50px;
+ }
+ }
+
+ // Connect second build to first build with smaller curved line
+ &:nth-child(2) {
+ &::after, &::before {
+ height: 45px;
+ top: -26px;
+ }
+ }
+ }
+
+ &:last-child {
+ .build {
+ // Remove right connecting horizontal line from first build in last stage
+ &:first-child {
+ &::after, &::before {
+ border: none;
+ }
+ }
+ // Remove right curved connectors from all builds in last stage
+ &:not(:first-child) {
+ &::after {
+ border: none;
+ }
+ }
+ }
+ }
+
+ &:first-child {
+ .build {
+ // Remove left curved connectors from all builds in first stage
+ &:not(:first-child) {
+ &::before {
+ border: none;
+ }
+ }
+ }
+ }
+}
+
+.pipeline-actions {
+ border-bottom: none;
+}
+
+.toggle-pipeline-btn {
+
+ .fa {
+ color: $dropdown-header-color;
+ }
+}
+
.pipelines.tab-pane {
.content-list.pipelines {
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index ea2f5f9281a..94df7d131ca 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -38,6 +38,10 @@ module CiStatusHelper
'icon_status_pending'
when 'running'
'icon_status_running'
+ when 'play'
+ return icon('play fw')
+ when 'created'
+ 'icon_status_pending'
else
'icon_status_cancel'
end
@@ -48,13 +52,13 @@ module CiStatusHelper
def render_commit_status(commit, tooltip_placement: 'auto left')
project = commit.project
path = builds_namespace_project_commit_path(project.namespace, project, commit)
- render_status_with_link('commit', commit.status, path, tooltip_placement)
+ render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
path = namespace_project_pipeline_path(project.namespace, project, pipeline)
- render_status_with_link('pipeline', pipeline.status, path, tooltip_placement)
+ render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
def no_runners_for_project?(project)
@@ -62,13 +66,17 @@ module CiStatusHelper
Ci::Runner.shared.blank?
end
- private
+ def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
+ klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
+ title = "#{type.titleize}: #{ci_label_for_status(status)}"
+ data = { toggle: 'tooltip', placement: tooltip_placement }
- def render_status_with_link(type, status, path, tooltip_placement, cssclass: '')
- link_to ci_icon_for_status(status),
- path,
- class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}",
- title: "#{type.titleize}: #{ci_label_for_status(status)}",
- data: { toggle: 'tooltip', placement: tooltip_placement }
+ if path
+ link_to ci_icon_for_status(status), path,
+ class: klass, title: title, data: data
+ else
+ content_tag :span, ci_icon_for_status(status),
+ class: klass, title: title, data: data
+ end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 3d6c6ea3209..096280ab617 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -97,7 +97,7 @@ module Ci
end
def playable?
- project.builds_enabled? && commands.present? && manual?
+ project.builds_enabled? && commands.present? && manual? && skipped?
end
def play(current_user = nil)
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 8cfba92ae9b..c3d22933683 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -72,6 +72,10 @@ module Ci
CommitStatus.where(pipeline: pluck(:id)).stages
end
+ def stages_with_latest_statuses
+ statuses.latest.order(:stage_idx).group_by(&:stage)
+ end
+
def project_id
project.id
end
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
new file mode 100644
index 00000000000..04cbd0c3591
--- /dev/null
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -0,0 +1,14 @@
+- is_playable = subject.playable? && can?(current_user, :update_build, @project)
+%li.build{class: ("playable" if is_playable)}
+ .build-content
+ - if is_playable
+ = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
+ = render_status_with_link('build', 'play')
+ = subject.name
+ - elsif can?(current_user, :read_build, @project)
+ = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
+ = render_status_with_link('build', subject.status)
+ = subject.name
+ - else
+ = render_status_with_link('build', subject.status)
+ = ci_icon_for_status(subject.status)
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 640abdb993f..20a85148ab5 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -1,5 +1,9 @@
-.row-content-block.build-content.middle-block
+.row-content-block.build-content.middle-block.pipeline-actions
.pull-right
+ .btn.btn-grouped.btn-white.toggle-pipeline-btn
+ %span.toggle-btn-text Hide
+ %span pipeline graph
+ %span.caret
- if can?(current_user, :update_pipeline, pipeline.project)
- if pipeline.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
@@ -23,6 +27,22 @@
in
= time_interval_in_words pipeline.duration
+.row-content-block.build-content.middle-block.pipeline-graph
+ .pipeline-visualization
+ %ul.stage-column-list
+ - stages = pipeline.stages_with_latest_statuses
+ - stages.each do |stage, statuses|
+ %li.stage-column
+ .stage-name
+ %a{name: stage}
+ - if stage
+ = stage.titleize
+ .builds-container
+ %ul
+ - statuses.each do |status|
+ = render "projects/#{status.to_partial_path}_pipeline", subject: status
+
+
- if pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger
%h4 Found errors in your .gitlab-ci.yml:
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
new file mode 100644
index 00000000000..584c0fa18ae
--- /dev/null
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -0,0 +1,9 @@
+%li.build
+ .build-content
+ - if subject.target_url
+ - link_to subject.target_url do
+ = render_status_with_link('commit status', subject.status)
+ = subject.name
+ - else
+ = render_status_with_link('commit status', subject.status)
+ = subject.name
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index 0d493fa1c3c..069d9dd6226 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,9 +1,8 @@
class Gitlab::Seeder::Builds
- STAGES = %w[build notify_build test notify_test deploy notify_deploy]
+ STAGES = %w[build test deploy notify]
BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success },
- { name: 'slack post build', stage: 'notify_build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
@@ -12,9 +11,9 @@ class Gitlab::Seeder::Builds
{ name: 'spinach:osx', stage: 'test', status: :canceled },
{ name: 'cucumber:linux', stage: 'test', status: :running },
{ name: 'cucumber:osx', stage: 'test', status: :failed },
- { name: 'slack post test', stage: 'notify_test', status: :success },
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
- { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success },
+ { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
+ { name: 'slack', stage: 'notify', when: 'manual', status: :created },
]
def initialize(project)
@@ -25,7 +24,7 @@ class Gitlab::Seeder::Builds
pipelines.each do |pipeline|
begin
BUILDS.each { |opts| build_create!(pipeline, opts) }
- commit_status_create!(pipeline, name: 'jenkins', status: :success)
+ commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success)
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb
index 29d150bc597..47482bc3cc9 100644
--- a/spec/features/projects/pipelines_spec.rb
+++ b/spec/features/projects/pipelines_spec.rb
@@ -193,7 +193,11 @@ describe "Pipelines" do
end
context 'playing manual build' do
- before { click_link('Play') }
+ before do
+ within '.pipeline-holder' do
+ click_link('Play')
+ end
+ end
it { expect(@manual.reload).to be_pending }
end