summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/stylesheets/framework/tables.scss16
-rw-r--r--app/controllers/projects/pipelines_controller.rb88
-rw-r--r--app/models/ability.rb7
-rw-r--r--app/models/ci/commit.rb11
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/ci/commits/_commit.html.haml75
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml30
-rw-r--r--app/views/projects/pipelines/_header_title.html.haml1
-rw-r--r--app/views/projects/pipelines/index.html.haml62
-rw-r--r--app/views/projects/pipelines/new.html.haml25
-rw-r--r--app/views/projects/pipelines/show.html.haml3
-rw-r--r--config/routes.rb7
-rw-r--r--spec/models/project_spec.rb2
14 files changed, 312 insertions, 26 deletions
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 75b770ae5a2..9d6a6c5b237 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -34,6 +34,22 @@ table {
font-weight: normal;
font-size: 15px;
border-bottom: 1px solid $border-color;
+
+ .rotate {
+ height: 140px;
+ white-space: nowrap;
+ transform:
+ /* Magic Numbers */
+ translate(25px, 51px)
+ /* 45 is really 360 - 45 */
+ rotate(315deg);
+ width: 30px;
+ }
+
+ .rotate > div > span {
+ border-bottom: 1px solid #ccc;
+ padding: 5px 10px;
+ }
}
td {
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
new file mode 100644
index 00000000000..b2ee5573bfc
--- /dev/null
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -0,0 +1,88 @@
+class Projects::PipelinesController < Projects::ApplicationController
+ before_action :pipeline, except: [:index, :new, :create]
+ before_action :authorize_read_pipeline!
+ before_action :authorize_create_pipeline!, only: [:new, :create]
+ before_action :authorize_update_pipeline!, only: [:retry, :cancel]
+ layout 'project'
+
+ def index
+ @scope = params[:scope]
+ @all_pipelines = project.ci_commits
+ @pipelines = @all_pipelines.order(id: :desc)
+ @pipelines =
+ case @scope
+ when 'running'
+ @pipelines.running_or_pending
+ when 'branches'
+ @branches = project.repository.branches.map(&:name)
+ @branches_ids = @all_pipelines.where(ref: @branches).group(:ref).select('max(id)')
+ @pipelines.where(id: @branches_ids)
+ when 'tags'
+ @tags = project.repository.tags.map(&:name)
+ @tags_ids = @all_pipelines.where(ref: @tags).group(:ref).select('max(id)')
+ @pipelines.where(id: @tags_ids)
+ else
+ @pipelines
+ end
+ @pipelines = @pipelines.page(params[:page]).per(30)
+ end
+
+ def new
+ end
+
+ def create
+ ref_names = project.repository.ref_names
+ unless ref_names.include?(params[:ref])
+ @error = 'Reference not found'
+ render action: 'new'
+ return
+ end
+
+ commit = project.commit(params[:ref])
+ unless commit
+ @error = 'Commit not found'
+ render action: 'new'
+ return
+ end
+
+ pipeline = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA)
+
+ # Skip creating ci_commit when no gitlab-ci.yml is found
+ unless pipeline.config_processor
+ @error = pipeline.yaml_errors || 'Missing .gitlab-ci.yml file'
+ render action: 'new'
+ return
+ end
+
+ Ci::Commit.transaction do
+ commit.save!
+ commit.create_builds(current_user)
+ end
+
+ redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id)
+ end
+
+ def show
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ def retry
+ pipeline.builds.latest.failed.select(&:retryable?).each(&:retry)
+
+ redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ end
+
+ def cancel
+ pipeline.builds.running_or_pending.each(&:cancel)
+
+ redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ end
+
+ private
+
+ def pipeline
+ @pipeline ||= project.ci_commits.find_by!(id: params[:id])
+ end
+end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index c0bf6def7c5..ec5ac54c277 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -195,6 +195,7 @@ class Ability
:admin_label,
:read_commit_status,
:read_build,
+ :read_pipeline,
]
end
@@ -206,6 +207,8 @@ class Ability
:update_commit_status,
:create_build,
:update_build,
+ :create_pipeline,
+ :update_pipeline,
:create_merge_request,
:create_wiki,
:push_code
@@ -234,7 +237,8 @@ class Ability
:admin_wiki,
:admin_project,
:admin_commit_status,
- :admin_build
+ :admin_build,
+ :admin_pipeline
]
end
@@ -277,6 +281,7 @@ class Ability
unless project.builds_enabled
rules += named_abilities('build')
+ rules += named_abilities('pipeline')
end
rules
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 8865bd76bd2..7991b987e35 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -94,6 +94,17 @@ module Ci
end
end
+ def latest?
+ return false unless ref
+ commit = project.commit(ref)
+ return false unless commit
+ commit.sha == sha
+ end
+
+ def triggered?
+ trigger_requests.any?
+ end
+
def invalidate
write_attribute(:status, nil)
write_attribute(:started_at, nil)
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..b58d8270230 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -39,6 +39,13 @@
Commits
- if project_nav_tab? :builds
+ = nav_link(controller: %w(ci_commits)) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do
+ = icon('ship fw')
+ %span
+ Pipelines
+ %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all))
+
= nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 218d396b898..7ded4828b2f 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -13,7 +13,9 @@
%strong ##{build.id}
- if build.stuck?
- %i.fa.fa-warning.text-warning
+ %i.fa.fa-warning.text-warning.has-tooltip(title="Build is stuck. Check runners.")
+ - if defined?(retried) && retried
+ %i.fa.fa-warning.has-tooltip(title="Build was retried")
- if defined?(commit_sha) && commit_sha
%td
diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml
new file mode 100644
index 00000000000..32f85cb8f8c
--- /dev/null
+++ b/app/views/projects/ci/commits/_commit.html.haml
@@ -0,0 +1,75 @@
+- status = commit.status
+%tr.commit
+ %td.commit-link
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do
+ = ci_icon_for_status(status)
+ %strong ##{commit.id}
+
+ %td
+ %div.branch-commit
+ - if commit.ref
+ = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace"
+ &middot;
+ = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"
+ &nbsp;
+ - if commit.latest?
+ %span.label.label-success latest
+ - if commit.tag?
+ %span.label.label-primary tag
+ - if commit.triggered?
+ %span.label.label-primary triggered
+ - if commit.yaml_errors.present?
+ %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid
+ - if commit.builds.any?(&:stuck?)
+ %span.label.label-warning stuck
+
+ %p
+ %span
+ - if commit_data = commit.commit_data
+ = link_to_gfm commit_data.title, namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
+ - else
+ Cant find HEAD commit for this branch
+
+
+ - stages_status = commit.statuses.stages_status
+ - stages.each do |stage|
+ %td
+ - if status = stages_status[stage]
+ %span.has-tooltip(title="#{stage.titleize}: #{status}"){class: "ci-status-icon-#{status}"}
+ = ci_icon_for_status(status)
+
+ %td
+ - if commit.started_at && commit.finished_at
+ %p
+ %i.fa.fa-clock-o
+ &nbsp;
+ #{duration_in_words(commit.finished_at, commit.started_at)}
+ - if commit.finished_at
+ %p
+ %i.fa.fa-calendar
+ &nbsp;
+ #{time_ago_with_tooltip(commit.finished_at)}
+
+ %td
+ .controls.hidden-xs.pull-right
+ - artifacts = commit.builds.latest
+ - if artifacts.present?
+ .dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon('download')
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - artifacts.each do |build|
+ %li
+ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span #{build.name}
+
+ - if can?(current_user, :update_pipeline, @project)
+ - if commit.retryable? && commit.builds.failed.any?
+ = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+ = icon("repeat")
+ &nbsp;
+ - if commit.active?
+ = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ = icon("remove cred")
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
index 25714e6cb47..2ec3c809e1c 100644
--- a/app/views/projects/commit/_ci_commit.html.haml
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -2,12 +2,15 @@
.pull-right
- if can?(current_user, :update_build, @project)
- if ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+ = link_to "Retry failed", retry_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post
- if ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+ = link_to "Cancel running", cancel_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
.oneline
+ Pipeline
+ = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace"
+ with
= pluralize ci_commit.statuses.count(:id), "build"
- if ci_commit.ref
for
@@ -17,7 +20,7 @@
for commit
= link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
- if ci_commit.duration > 0
- in
+ took
= time_interval_in_words ci_commit.duration
- if ci_commit.yaml_errors.present?
@@ -47,23 +50,4 @@
%th
- builds = ci_commit.statuses.latest.ordered
= render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
-
-- if ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @project.build_coverage_enabled?
- %th Coverage
- %th
- = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
+ = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false, retried: true
diff --git a/app/views/projects/pipelines/_header_title.html.haml b/app/views/projects/pipelines/_header_title.html.haml
new file mode 100644
index 00000000000..faf63d64a79
--- /dev/null
+++ b/app/views/projects/pipelines/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Pipelines", project_pipelines_path(@project))
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
new file mode 100644
index 00000000000..838b2986d4f
--- /dev/null
+++ b/app/views/projects/pipelines/index.html.haml
@@ -0,0 +1,62 @@
+- page_title "Pipelines"
+= render "header_title"
+
+.top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_pipelines_path(@project) do
+ All
+ %span.badge.js-totalbuilds-count
+ = number_with_delimiter(@all_pipelines.count)
+
+ %li{class: ('active' if @scope == 'running')}
+ = link_to project_pipelines_path(@project, scope: :running) do
+ Running
+ %span.badge.js-running-count
+ = number_with_delimiter(@all_pipelines.running_or_pending.count)
+
+ %li{class: ('active' if @scope == 'branches')}
+ = link_to project_pipelines_path(@project, scope: :branches) do
+ Branches
+
+ %li{class: ('active' if @scope == 'tags')}
+ = link_to project_pipelines_path(@project, scope: :tags) do
+ Tags
+
+ .nav-controls
+ - if can? current_user, :create_pipeline, @project
+ = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
+ = icon('plus')
+ New
+
+ - if can?(current_user, :update_build, @project)
+ - unless @repository.gitlab_ci_yml
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+
+ = link_to ci_lint_path, class: 'btn btn-default' do
+ = icon('wrench')
+ %span CI Lint
+
+.gray-content-block
+ Pipelines for #{(@scope || 'changes')} on this project
+
+%ul.content-list
+ - stages = @pipelines.stages
+ - if @pipelines.blank?
+ %li
+ .nothing-here-block No pipelines to show
+ - else
+ .table-holder
+ %table.table.builds
+ %tbody
+ %th Pipeline ID
+ %th Commit
+ - @pipelines.stages.each do |stage|
+ %th
+ %span.has-tooltip(title="#{stage.titleize}")
+ = truncate(stage.titleize.pluralize, length: 8)
+ %th
+ %th
+ = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
+
+ = paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
new file mode 100644
index 00000000000..39b1571b9cf
--- /dev/null
+++ b/app/views/projects/pipelines/new.html.haml
@@ -0,0 +1,25 @@
+- page_title "New Pipeline"
+= render "header_title"
+
+- if @error
+ .alert.alert-danger
+ %button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
+ = @error
+%h3.page-title
+ New Pipeline
+%hr
+
+= form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do
+ .form-group
+ = label_tag :ref, 'Create for', class: 'control-label'
+ .col-sm-10
+ = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+ .help-block Existing branch name, tag
+ .form-actions
+ = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel'
+
+:javascript
+ var availableRefs = #{@project.repository.ref_names.to_json};
+
+ new NewBranchForm($('.js-create-branch-form'), availableRefs)
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
new file mode 100644
index 00000000000..9f33e2ad624
--- /dev/null
+++ b/app/views/projects/pipelines/show.html.haml
@@ -0,0 +1,3 @@
+- page_title "Pipeline"
+= render "header_title"
+= render "projects/commit/ci_commit", ci_commit: @pipeline
diff --git a/config/routes.rb b/config/routes.rb
index 48601b7567b..0932739473d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -656,6 +656,13 @@ Rails.application.routes.draw do
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy]
+ resources :pipelines, only: [:index, :new, :create, :show] do
+ member do
+ post :cancel
+ post :retry
+ end
+ end
+
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1688e91ca62..59df2c5cb87 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -62,7 +62,7 @@ describe Project, models: true do
it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:commit_statuses) }
- it { is_expected.to have_many(:ci_commits) }
+ it { is_expected.to have_many(:pipelines) }
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) }