From 95b010fb73832b1bcfba884b9bcfd87d2954e44c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 31 Mar 2016 19:51:28 +0200 Subject: [PATCH 1/2] WIP --- .../projects/ci_commits_controller.rb | 43 ++++ app/controllers/projects/commit_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 3 + app/helpers/gitlab_routing_helper.rb | 4 + app/models/ci/build.rb | 4 +- app/models/ci/commit.rb | 115 ++++++---- app/models/commit.rb | 10 +- app/models/commit_status.rb | 28 +-- app/models/concerns/ci_status.rb | 50 ++++ app/models/merge_request.rb | 2 +- app/models/project.rb | 8 +- .../ci/create_trigger_request_service.rb | 2 +- app/services/create_commit_builds_service.rb | 9 +- app/views/layouts/nav/_project.html.haml | 8 + app/views/projects/_last_commit.html.haml | 9 +- app/views/projects/ci/builds/_build.html.haml | 15 +- .../projects/ci/commits/_commit.html.haml | 141 ++++++++++++ .../ci_commits/_header_title.html.haml | 1 + app/views/projects/ci_commits/index.html.haml | 51 +++++ app/views/projects/ci_commits/show.html.haml | 216 ++++++++++++++++++ app/views/projects/commit/_builds.html.haml | 69 +----- .../projects/commit/_ci_commit.html.haml | 69 ++++++ .../projects/commit/_commit_box.html.haml | 8 +- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 7 +- .../issues/_related_branches.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 7 +- config/routes.rb | 2 + .../20160331153918_add_fields_to_ci_commit.rb | 7 + lib/api/commit_statuses.rb | 2 +- 30 files changed, 718 insertions(+), 183 deletions(-) create mode 100644 app/controllers/projects/ci_commits_controller.rb create mode 100644 app/models/concerns/ci_status.rb create mode 100644 app/views/projects/ci/commits/_commit.html.haml create mode 100644 app/views/projects/ci_commits/_header_title.html.haml create mode 100644 app/views/projects/ci_commits/index.html.haml create mode 100644 app/views/projects/ci_commits/show.html.haml create mode 100644 app/views/projects/commit/_ci_commit.html.haml create mode 100644 db/migrate/20160331153918_add_fields_to_ci_commit.rb diff --git a/app/controllers/projects/ci_commits_controller.rb b/app/controllers/projects/ci_commits_controller.rb new file mode 100644 index 000000000000..e4797833b066 --- /dev/null +++ b/app/controllers/projects/ci_commits_controller.rb @@ -0,0 +1,43 @@ +class Projects::CiCommitsController < Projects::ApplicationController + before_action :ci_commit, except: [:index] + before_action :authorize_read_build! + layout 'project' + + def index + @scope = params[:scope] + @all_commits = project.ci_commits + @commits = @all_commits.order(id: :desc) + @commits = + case @scope + when 'latest' + @commits + when 'branches' + refs = project.repository.branches.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + when 'tags' + refs = project.repository.tags.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + else + @commits + end + @commits = @commits.page(params[:page]).per(30) + end + + def show + @commit = @ci_commit.commit + @builds = @ci_commit.builds + @statuses = @ci_commit.statuses + + respond_to do |format| + format.html + end + end + + private + + def ci_commit + @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + end +end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb2b..ef20281e82f0 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -94,8 +94,8 @@ def commit @commit ||= @project.commit(params[:id]) end - def ci_commit - @ci_commit ||= project.ci_commit(commit.sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: commit.sha) end def define_show_vars @@ -108,7 +108,8 @@ def define_show_vars @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count - @statuses = ci_commit.statuses if ci_commit + @statuses = ci_commits.statuses + @builds = @statuses end def assign_revert_commit_vars diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 62451ac73a9d..b9618d168b67 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -118,6 +118,7 @@ def new @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit @note_counts = Note.where(commit_id: @commits.map(&:id)). @@ -300,6 +301,7 @@ def define_show_vars @merge_request_diff = @merge_request.merge_request_diff @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit if @merge_request.locked_long_ago? @@ -310,6 +312,7 @@ def define_show_vars def define_widget_vars @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact closes_issues end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f3fddef01cb8..e4580eea524c 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,6 +25,10 @@ def project_commits_path(project, *args) namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_ci_commits_path(project, *args) + namespace_project_ci_commits_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7d33838044b5..c99aeff6f1cc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -126,12 +126,12 @@ def retryable? end def retried? - !self.commit.latest_statuses_for_ref(self.ref).include?(self) + !self.commit.latest.include?(self) end def depends_on_builds # Get builds of the same type - latest_builds = self.commit.builds.similar(self).latest + latest_builds = self.commit.builds.latest # Return builds from previous stages latest_builds.where('stage_idx < ?', stage_idx) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f4cf7034b14a..cd9ce0a283d0 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,19 +19,54 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model + include CiStatus belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + scope :running, -> { where(status: 'running') } + scope :pending, -> { where(status: 'pending') } + scope :success, -> { where(status: 'success') } + scope :failed, -> { where(status: 'failed') } + scope :running_or_pending, -> { where(status: [:running, :pending]) } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + validates_presence_of :sha + validates_presence_of :before_sha + validates_presence_of :ref validate :valid_commit_sha + # Make sure that status is saved + before_save :status + + def ignored? + false + end + def self.truncate_sha(sha) sha[0...8] end + def self.retried + all.map { |commit| commit.retried }.flatten + end + + def self.statuses + CommitStatus.where(commit: all) + end + + def self.builds + Ci::Build.where(commit: all) + end + + def self.stages + CommitStatus.where(commit: all) + .group(:stage, :stage_idx).order(:stage_idx) + .pluck(:stage, :stage_idx).map(&:first) + end + def to_param sha end @@ -68,9 +103,16 @@ def commit_data nil end - def stage - running_or_pending = statuses.latest.running_or_pending.ordered - running_or_pending.first.try(:stage) + def origin_ref + ref + end + + def tag? + Gitlab::Git::tag_ref?(origin_ref) + end + + def branch? + Gitlab::Git::branch_ref?(origin_ref) end def create_builds(ref, tag, user, trigger_request = nil) @@ -101,16 +143,8 @@ def create_next_builds(build) end end - def refs - statuses.order(:ref).pluck(:ref).uniq - end - - def latest_statuses - @latest_statuses ||= statuses.latest.to_a - end - - def latest_statuses_for_ref(ref) - latest_statuses.select { |status| status.ref == ref } + def latest + statuses.latest end def matrix_builds(build = nil) @@ -124,52 +158,39 @@ def retried end def status - if yaml_errors.present? - return 'failed' - end - - @status ||= Ci::Status.get_status(latest_statuses) - end - - def pending? - status == 'pending' + read_attribute(:status) || update_status end - def running? - status == 'running' - end - - def success? - status == 'success' - end - - def failed? - status == 'failed' - end - - def canceled? - status == 'canceled' + def duration + duration_array = latest.map(&:duration).compact + duration_array.reduce(:+).to_i end - def active? - running? || pending? + def started_at + read_attribute(:started_at) || update_started_at end - def complete? - canceled? || success? || failed? + def finished_at + read_attribute(:finished_at) || update_finished_at end - def duration - duration_array = statuses.map(&:duration).compact - duration_array.reduce(:+).to_i + def update_status + status = + if yaml_errors.present? + 'failed' + else + latest.status + end end - def started_at - @started_at ||= statuses.order('started_at ASC').first.try(:started_at) + def update_started_at + started_at = + statuses.order(:id).first.try(:started_at) end - def finished_at - @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) + def update_finished_at + finished_at = + statuses.order(id: :desc).first.try(:finished_at) end def coverage diff --git a/app/models/commit.rb b/app/models/commit.rb index d0dbe009d0d1..6fdf85362c95 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -10,6 +10,9 @@ class Commit attr_mentionable :safe_message, pipeline: :single_line participant :author, :committer, :notes + alias_attribute :sha, :id + alias_attribute :short_sha, :short_id + attr_accessor :project DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] @@ -209,12 +212,13 @@ def short_id @raw.short_id(7) end - def ci_commit - project.ci_commit(sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: sha) end def status - ci_commit.try(:status) || :not_found + return @status if defined?(@status) + @status ||= ci_commits.status end def revert_branch_name diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3377a85a55a7..3a059f01d494 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,6 +33,8 @@ # class CommitStatus < ActiveRecord::Base + include CiStatus + self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id @@ -40,7 +42,6 @@ class CommitStatus < ActiveRecord::Base belongs_to :user validates :commit, presence: true - validates :status, inclusion: { in: %w(pending running failed success canceled) } validates_presence_of :name @@ -86,32 +87,9 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' - end - - delegate :sha, :short_sha, to: :commit, prefix: false - - # TODO: this should be removed with all references - def before_sha - Gitlab::Git::BLANK_SHA end - def started? - !pending? && !canceled? && started_at - end - - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? - end + delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false def ignored? allow_failure? && (failed? || canceled?) diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb new file mode 100644 index 000000000000..494db0250483 --- /dev/null +++ b/app/models/concerns/ci_status.rb @@ -0,0 +1,50 @@ +module CiStatus + extend ActiveSupport::Concern + + module ClassMethods + def status + if all.none? + nil + elsif all.all? { |status| status.success? || status.ignored? } + 'success' + elsif all.all?(&:pending?) + 'pending' + elsif all.any?(&:running?) || all.any?(&:pending?) + 'running' + elsif all.all?(&:canceled?) + 'canceled' + else + 'failed' + end + end + + def duration + duration_array = all.map(&:duration).compact + duration_array.reduce(:+).to_i + end + end + + included do + validates :status, inclusion: { in: %w(pending running failed success canceled) } + + state_machine :status, initial: :pending do + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + end + end + + def started? + !pending? && !canceled? && started_at + end + + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end +end \ No newline at end of file diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1245cc16d6ad..00d8b58c5fe4 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -588,7 +588,7 @@ def diverged_from_target_branch? end def ci_commit - @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project + @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project end def diff_refs diff --git a/app/models/project.rb b/app/models/project.rb index 2285063ab507..8897730f8f35 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -917,12 +917,12 @@ def allowed_to_share_with_group? !namespace.share_with_group_lock end - def ci_commit(sha) - ci_commits.find_by(sha: sha) + def ci_commit(sha, ref) + ci_commits.find_by(sha: sha, ref: ref) end - def ensure_ci_commit(sha) - ci_commit(sha) || ci_commits.create(sha: sha) + def ensure_ci_commit(sha, ref) + ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref) end def enable_ci diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index b3dfc707221b..483cc0a72a38 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,7 +7,7 @@ def execute(project, trigger, ref, variables = nil) # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ensure_ci_commit(commit.sha) + ci_commit = project.ensure_ci_commit(commit.sha, ref) trigger_request = trigger.trigger_requests.create!( variables: variables, diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index 69d5c42a8775..e7e1134ce4bc 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -2,6 +2,7 @@ class CreateCommitBuildsService def execute(project, user, params) return false unless project.builds_enabled? + before_sha = params[:checkout_sha] || params[:before] sha = params[:checkout_sha] || params[:after] origin_ref = params[:ref] @@ -10,15 +11,16 @@ def execute(project, user, params) end ref = Gitlab::Git.ref_name(origin_ref) + tag = Gitlab::Git.tag_ref?(origin_ref) # Skip branch removal if sha == Gitlab::Git::BLANK_SHA return false end - commit = project.ci_commit(sha) + commit = project.ci_commit(sha, ref) unless commit - commit = project.ci_commits.new(sha: sha) + commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag) # Skip creating ci_commit when no gitlab-ci.yml is found unless commit.ci_yaml_file @@ -32,8 +34,7 @@ def execute(project, user, params) # Skip creating builds for commits that have [ci skip] unless commit.skip_ci? # Create builds for commit - tag = Gitlab::Git.tag_ref?(origin_ref) - commit.create_builds(ref, tag, user) + commit.create_builds(user) end commit diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75ec..bb0bba77fa22 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -38,6 +38,14 @@ %span Commits + - if project_nav_tab? :builds + = nav_link(controller: %w(ci_commits)) do + = link_to project_ci_commits_path(@project), title: 'CI', class: 'shortcuts-ci' do + = icon('cubes fw') + %span + CI + %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) + - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e77872..47d9239eaa2b 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,9 +1,8 @@ .project-last-commit - - ci_commit = project.ci_commit(commit.sha) - - if ci_commit - = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do - = ci_status_icon(ci_commit) - = ci_status_label(ci_commit) + - if commit.status + = link_to ci_status_path(commit), class: "ci-status ci-#{commit.status}" do + = ci_status_icon(commit) + = ci_status_label(commit) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index d22d1da8402b..9448f248163e 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,4 +1,4 @@ -%tr.build +%tr.build{style:"text-decoration: #{if defined?(retried) && retried then "line-through" else "inherit" end}"} %td.status - if can?(current_user, :read_build, build) = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) @@ -19,11 +19,12 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none + - if !defined?(ref) || ref + %td + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) + - else + .light none - if defined?(runner) && runner %td @@ -48,6 +49,8 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail + - if defined?(retried) && retried + %span.label.label-warning retried %td.duration - if build.duration 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 000000000000..671c5abcbed2 --- /dev/null +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -0,0 +1,141 @@ +%tr.commit + %td.commit-link + - if can?(current_user, :read_commit, commit) + = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do + %strong ##{commit.id} + - else + %strong ##{commit.id} + + %td.status + %div + - if can?(current_user, :read_commit, commit) + = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) + - else + = ci_status_with_icon(commit.status) + + %td + %div + - if commit.ref + = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) +   + - if commit.tag? + %span.label.label-primary tag + - if commit.branch? + %span.label.label-primary branch + - if commit.trigger_requests.any? + %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 + + - if commit_data = commit.commit_data + = render 'projects/branches/commit', commit: commit_data, project: @project + - else + %p + Cant find HEAD commit for this branch + + %td + %div + Duration: +   + - if commit.started_at && commit.finished_at + #{duration_in_words(commit.finished_at, commit.started_at)} + - else + \- + %p + Finished: +   + - if commit.finished_at + #{time_ago_with_tooltip(commit.finished_at)} + - else + \- + + %td.content + .controls.hidden-xs + = link_to project_builds_path(commit.project), class: 'btn btn-grouped btn-xs' do + = icon('cubes fw') + Details + + - @project = commit.project + - ref = 'master' + %span.btn-group.btn-grouped + %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Archive Format + %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download + %span Download zip + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.gz + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.bz2 + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar +-#%tr.commit +-# %td.status +-# - if can?(current_user, :read_commit, commit) +-# = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) +-# - else +-# = ci_status_with_icon(commit.status) +-# +-# %td.commit-link +-# - if can?(current_user, :read_commit, commit) +-# = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do +-# %strong ##{commit.id} +-# - else +-# %strong ##{commit.id} +-# +-# - if defined?(commit_sha) && commit_sha +-# %td +-# = link_to commit.short_sha, namespace_project_commit_path(commit.project.namespace, commit.project, commit.sha), class: "monospace" +-# +-# %td +-# - if commit.ref +-# = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) +-# - else +-# .light none +-# +-# %td +-# = commit.git_commit_message +-# +-# %td.duration +-# - if commit.started_at && commit.finished_at +-# #{duration_in_words(commit.finished_at, commit.started_at)} +-# +-# %td.timestamp +-# - if commit.finished_at +-# %span #{time_ago_with_tooltip(commit.finished_at)} +-# +-# - if defined?(coverage) && coverage +-# %td.coverage +-# - if commit.try(:coverage) +-# #{commit.coverage}% +-# +-# %td +-# .pull-right +-# - if can?(current_user, :read_commit, commit) && commit.artifacts? +-# = link_to download_namespace_project_commit_artifacts_path(commit.project.namespace, commit.project, commit), title: 'Download artifacts' do +-# %i.fa.fa-download +-# - if can?(current_user, :update_commit, commit) +-# - if commit.active? +-# = link_to cancel_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Cancel' do +-# %i.fa.fa-remove.cred +-# - elsif defined?(allow_retry) && allow_retry && commit.retryable? +-# = link_to retry_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Retry' do +-# %i.fa.fa-repeat +-# +-#- if commit.yaml_errors.present? +-# %tr +-# %td{colspan: 7} +-# .light +-# = commit.yaml_errors \ No newline at end of file diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml new file mode 100644 index 000000000000..c04fc6be9f1e --- /dev/null +++ b/app/views/projects/ci_commits/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "CI", project_ci_commits_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml new file mode 100644 index 000000000000..7955e5e9abfb --- /dev/null +++ b/app/views/projects/ci_commits/index.html.haml @@ -0,0 +1,51 @@ +- page_title "CI Changes" += render "header_title" + +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil? || @scope == 'latest')} + = link_to project_ci_commits_path(@project, scope: :latest) do + Latest + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_commits.count(:id)) + + %li{class: ('active' if @scope == 'branches')} + = link_to project_ci_commits_path(@project, scope: :branches) do + Branches + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'tags')} + = link_to project_ci_commits_path(@project, scope: :tags) do + Tags + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'failed')} + = link_to project_ci_commits_path(@project, scope: :failed) do + Failed + %span.badge.js-running-count + = number_with_delimiter(@all_commits.finished.count(:id)) + + .nav-controls + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Builds', 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 + #{(@scope || 'running').capitalize} changes on this project + +%ul.content-list + - if @commits.blank? + %li + .nothing-here-block No commits to show + - else + .table-holder + %table.table.builds + = render @commits, commit_sha: true, stage: true, allow_retry: true + + = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/show.html.haml b/app/views/projects/ci_commits/show.html.haml new file mode 100644 index 000000000000..b02aee3db212 --- /dev/null +++ b/app/views/projects/ci_commits/show.html.haml @@ -0,0 +1,216 @@ +- page_title "#{@build.name} (##{@build.id})", "Builds" += render "header_title" + +.build-page + .gray-content-block.top-block + Build ##{@build.id} for commit + %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) + from + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) + - merge_request = @build.merge_request + - if merge_request + via + = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) + + #up-build-trace + - builds = @build.commit.matrix_builds(@build) + - if builds.size > 1 + %ul.nav-links.no-top.no-bottom + - builds.each do |build| + %li{class: ('active' if build == @build) } + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + - if @build.retried? + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning + This build was retried. + + .gray-content-block.middle-block + .build-head + .clearfix + = ci_status_with_icon(@build.status) + - if @build.duration + %span + %i.fa.fa-time + #{duration_in_words(@build.finished_at, @build.started_at)} + .pull-right + #{time_ago_with_tooltip(@build.finished_at) if @build.finished_at} + + - if @build.stuck? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have any runners online assigned to it. + - elsif @build.tags.any? + This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners that can run this build. + + %br + Go to + = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + Runners page + + .row.prepend-top-default + .col-md-9 + .clearfix + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + .clearfix + #js-build-scroll.scroll-controls + = link_to '#up-build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + + - if @build.erased? + .erased.alert.alert-warning + - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by + Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} + - else + %pre.trace#build-trace + %code.bash + = preserve do + = raw @build.trace_html + + %div#down-build-trace + + .col-md-3 + - if @build.coverage + .build-widget + %h4.title + Test coverage + %h1 #{@build.coverage}% + + - if can?(current_user, :read_build, @project) && @build.artifacts? + .build-widget.artifacts + %h4.title Build artifacts + .center + .btn-group{ role: :group } + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do + = icon('download') + Download + + - if @build.artifacts_metadata? + = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do + = icon('folder-open') + Browse + + .build-widget + %h4.title + Build ##{@build.id} + - if can?(current_user, :update_build, @project) + .center + .btn-group{ role: :group } + - if @build.active? + = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post + - elsif @build.retryable? + = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post + + - if @build.erasable? + = link_to erase_namespace_project_build_path(@project.namespace, @project, @build), + class: 'btn btn-sm btn-warning', method: :post, + data: { confirm: 'Are you sure you want to erase this build?' } do + = icon('eraser') + Erase + + .clearfix + - if @build.duration + %p + %span.attr-name Duration: + #{duration_in_words(@build.finished_at, @build.started_at)} + %p + %span.attr-name Created: + #{time_ago_with_tooltip(@build.created_at)} + - if @build.finished_at + %p + %span.attr-name Finished: + #{time_ago_with_tooltip(@build.finished_at)} + - if @build.erased_at + %p + %span.attr-name Erased: + #{time_ago_with_tooltip(@build.erased_at)} + %p + %span.attr-name Runner: + - if @build.runner && current_user && current_user.admin + = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id) + - elsif @build.runner + \##{@build.runner.id} + + - if @build.trigger_request + .build-widget + %h4.title + Trigger + + %p + %span.attr-name Token: + #{@build.trigger_request.trigger.short_token} + + - if @build.trigger_request.variables + %p + %span.attr-name Variables: + + %code + - @build.trigger_request.variables.each do |key, value| + #{key}=#{value} + + .build-widget + %h4.title + Commit + .pull-right + %small + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + %p + %span.attr-name Branch: + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) + %p + %span.attr-name Author: + #{@build.commit.git_author_name} + %p + %span.attr-name Message: + #{@build.commit.git_commit_message} + + - if @build.tags.any? + .build-widget + %h4.title + Tags + - @build.tag_list.each do |tag| + %span.label.label-primary + = tag + + - if @builds.present? + .build-widget + %h4.title #{pluralize(@builds.count(:id), "other build")} for + = succeed ":" do + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + %table.table.builds + - @builds.each_with_index do |build, i| + %tr.build + %td + = ci_icon_for_status(build.status) + %td + = link_to namespace_project_build_path(@project.namespace, @project, build) do + - if build.name + = build.name + - else + %span ##{build.id} + + %td.status= build.status + + + :javascript + new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 003b7c18d0e6..5c9a319edebe 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,67 +1,2 @@ -.gray-content-block.middle-block - .pull-right - - if can?(current_user, :update_build, @ci_commit.project) - - if @ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), 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(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - - .oneline - = pluralize @statuses.count(:id), "build" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace" - - if @ci_commit.duration > 0 - in - = time_interval_in_words @ci_commit.duration - -- if @ci_commit.yaml_errors.present? - .bs-callout.bs-callout-danger - %h4 Found errors in your .gitlab-ci.yml: - %ul - - @ci_commit.yaml_errors.split(",").each do |error| - %li= error - You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} - -- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file - .bs-callout.bs-callout-warning - \.gitlab-ci.yml not found in this commit - -.table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - - @ci_commit.refs.each do |ref| - - builds = @ci_commit.statuses.for_ref(ref).latest.ordered - = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, 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 @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true +- @ci_commits.each do |ci_commit| + = render "ci_commit", ci_commit: ci_commit diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml new file mode 100644 index 000000000000..06520e40bd96 --- /dev/null +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -0,0 +1,69 @@ +.gray-content-block.middle-block + .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, @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, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + + .oneline + = pluralize ci_commit.statuses.count(:id), "build" + - if ci_commit.ref + for + %span.label.label-info + = ci_commit.ref + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + - if ci_commit.duration > 0 + in + = time_interval_in_words ci_commit.duration + +- if ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - ci_commit.yaml_errors.split(",").each do |error| + %li= error + You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} + +- if @project.builds_enabled? && !ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +.table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %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 diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 71995fcc487a..f1da308b47bd 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -42,12 +42,12 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @ci_commit +- if @commit.status .pull-right - = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do - = ci_status_icon(@ci_commit) + = link_to ci_status_path(@commit), class: "ci-status ci-#{@commit.status}" do + = ci_status_icon(@commit) build: - = ci_status_label(@ci_commit) + = ci_status_label(@commit) .commit-info-row.branches %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 21e186120c37..096f7058bd43 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -5,7 +5,7 @@ .prepend-top-default = render "commit_box" -- if @ci_commit +- if @commit.status = render "ci_menu" - else %div.block-connector diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a90..fa34f7b7d611 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,9 +4,8 @@ - notes = commit.notes - note_count = notes.user.count -- ci_commit = project.ci_commit(commit.sha) - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(commit.status) if commit.status = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } @@ -17,8 +16,8 @@ %a.text-expander.js-toggle-button ... .pull-right - - if ci_commit - = render_ci_status(ci_commit) + - if commit.status + = render_ci_status(commit)   = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index b10cd03515f2..bdfa0c7009ee 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - sha = @project.repository.find_branch(branch).target - - ci_commit = @project.ci_commit(sha) if sha + - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status = render_ci_status(ci_commit) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53ff8959bc8c..53261fcace75 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -6,9 +6,8 @@ - css_class = '' unless local_assigns[:css_class] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description -- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(project.commit.status) if project.commit.status %li.project-row{ class: css_class } = cache(cache_key) do @@ -16,9 +15,9 @@ - if project.main_language %span = project.main_language - - if ci_commit + - if project.commit.status %span - = render_ci_status(ci_commit) + = render_ci_status(project.commit) - if forks %span = icon('code-fork') diff --git a/config/routes.rb b/config/routes.rb index 6bf22fb44567..b5d54871cf7e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -653,6 +653,8 @@ resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] + resources :ci_commits, only: [:index, :show] + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160331153918_add_fields_to_ci_commit.rb new file mode 100644 index 000000000000..03eb9ba4e531 --- /dev/null +++ b/db/migrate/20160331153918_add_fields_to_ci_commit.rb @@ -0,0 +1,7 @@ +class AddFieldsToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :status, :string + add_column :ci_commits, :started_at, :timestamp + add_column :ci_commits, :finished_at, :timestamp + end +end diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 8e74e177ea05..e7d76764ff58 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -21,7 +21,7 @@ class CommitStatus < Grape::API authorize!(:read_commit_status, user_project) not_found!('Commit') unless user_project.commit(params[:sha]) - ci_commit = user_project.ci_commit(params[:sha]) + ci_commit = user_project.ci_commit(params[:sha], params[:ref]) return [] unless ci_commit statuses = ci_commit.statuses -- GitLab From feec3a45eb517607d96f5553f36e7d35c97520ba Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 1 Apr 2016 10:10:39 +0200 Subject: [PATCH 2/2] Show pipelines --- app/controllers/projects/builds_controller.rb | 28 +++ .../projects/ci_commits_controller.rb | 63 ++++- app/controllers/projects/commit_controller.rb | 5 + app/models/ability.rb | 7 +- app/models/ci/build.rb | 4 + app/models/ci/commit.rb | 12 + app/models/commit_status.rb | 2 +- app/models/concerns/ci_status.rb | 11 +- app/views/layouts/nav/_project.html.haml | 9 +- .../projects/ci/commits/_commit.html.haml | 148 +++--------- .../ci_commits/_header_title.html.haml | 2 +- app/views/projects/ci_commits/index.html.haml | 36 ++- app/views/projects/ci_commits/new.html.haml | 25 ++ app/views/projects/ci_commits/show.html.haml | 216 ------------------ config/routes.rb | 8 +- .../20160331204039_add_action_to_ci_commit.rb | 5 + 16 files changed, 228 insertions(+), 353 deletions(-) create mode 100644 app/views/projects/ci_commits/new.html.haml delete mode 100644 app/views/projects/ci_commits/show.html.haml create mode 100644 db/migrate/20160331204039_add_action_to_ci_commit.rb diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index f159e169f6d0..269c0d3c21f1 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -20,6 +20,30 @@ def index @builds = @builds.page(params[:page]).per(30) end + def commits + @scope = params[:scope] + @all_commits = project.ci_commits + @commits = @all_commits.order(id: :desc) + @commits = + case @scope + when 'latest' + @commits + when 'branches' + refs = project.repository.branches.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + when 'tags' + refs = project.repository.tags.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + else + @commits + end + @commits = @commits.page(params[:page]).per(30) + end + + private + def cancel_all @project.builds.running_or_pending.each(&:cancel) redirect_to namespace_project_builds_path(project.namespace, project) @@ -68,6 +92,10 @@ def build @build ||= project.builds.unscoped.find_by!(id: params[:id]) end + def ci_commit + @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + end + def build_path(build) namespace_project_build_path(build.project.namespace, build.project, build) end diff --git a/app/controllers/projects/ci_commits_controller.rb b/app/controllers/projects/ci_commits_controller.rb index e4797833b066..a36e3085e8a1 100644 --- a/app/controllers/projects/ci_commits_controller.rb +++ b/app/controllers/projects/ci_commits_controller.rb @@ -1,6 +1,8 @@ class Projects::CiCommitsController < Projects::ApplicationController - before_action :ci_commit, except: [:index] - before_action :authorize_read_build! + before_action :ci_commit, 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 @@ -11,6 +13,8 @@ def index case @scope when 'latest' @commits + when 'running' + @commits.running_or_pending when 'branches' refs = project.repository.branches.map(&:name) ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') @@ -25,6 +29,47 @@ def index @commits = @commits.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 + + ci_commit = project.ci_commit(commit.id, params[:ref]) + if ci_commit + @error = 'Pipeline already created' + render action: 'new' + return + end + + # Skip creating ci_commit when no gitlab-ci.yml is found + commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) + unless commit.config_processor + @error = commit.yaml_errors || 'Missing .gitlab-ci.yml file' + render action: 'new' + return + end + + Ci::Commit.transaction do + commit.save! + commit.create_builds(params[:ref], false, current_user) + end + + redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) + end + def show @commit = @ci_commit.commit @builds = @ci_commit.builds @@ -35,6 +80,20 @@ def show end end + def retry + ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry) + + redirect_back_or_default default: namespace_project_ci_commits_path(project.namespace, project) + end + + def cancel + ci_commit.builds.running_or_pending.each(&:cancel) + + redirect_back_or_default default: namespace_project_ci_commits_path(project.namespace, project) + end + + def retry_builds + end private def ci_commit diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index ef20281e82f0..623856f282f3 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -96,6 +96,11 @@ def commit def ci_commits @ci_commits ||= project.ci_commits.where(sha: commit.sha) + if params[:commit_id] + @ci_commits.where(id: params[:commit_id].to_i) + else + @ci_commits + end end def define_show_vars diff --git a/app/models/ability.rb b/app/models/ability.rb index c0bf6def7c53..ec5ac54c2776 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -195,6 +195,7 @@ def project_report_rules :admin_label, :read_commit_status, :read_build, + :read_pipeline, ] end @@ -206,6 +207,8 @@ def project_dev_rules :update_commit_status, :create_build, :update_build, + :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code @@ -234,7 +237,8 @@ def project_master_rules :admin_wiki, :admin_project, :admin_commit_status, - :admin_build + :admin_build, + :admin_pipeline ] end @@ -277,6 +281,7 @@ def project_disabled_features_rules(project) unless project.builds_enabled rules += named_abilities('build') + rules += named_abilities('pipeline') end rules diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index c99aeff6f1cc..d497cf67cbcc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -129,6 +129,10 @@ def retried? !self.commit.latest.include?(self) end + def retry + Ci::Build.retry(self) + end + def depends_on_builds # Get builds of the same type latest_builds = self.commit.builds.latest diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index cd9ce0a283d0..45309614115d 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -67,6 +67,12 @@ def self.stages .pluck(:stage, :stage_idx).map(&:first) end + def stages + statuses + .group(:stage, :stage_idx).order(:stage_idx) + .pluck(:stage, :stage_idx).map(&:first) + end + def to_param sha end @@ -115,6 +121,12 @@ def branch? Gitlab::Git::branch_ref?(origin_ref) end + def retryable? + builds.latest.any? do |build| + build.failed? || build.retryable? + end + end + def create_builds(ref, tag, user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3a059f01d494..88cb7e21d66f 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -53,7 +53,7 @@ class CommitStatus < ActiveRecord::Base scope :failed, -> { where(status: 'failed') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :for_ref, ->(ref) { where(ref: ref) } diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 494db0250483..f7a9af1507d2 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -3,15 +3,16 @@ module CiStatus module ClassMethods def status - if all.none? + objs = all.to_a + if objs.none? nil - elsif all.all? { |status| status.success? || status.ignored? } + elsif objs.all? { |status| status.success? || status.ignored? } 'success' - elsif all.all?(&:pending?) + elsif objs.all?(&:pending?) 'pending' - elsif all.any?(&:running?) || all.any?(&:pending?) + elsif objs.any?(&:running?) || all.any?(&:pending?) 'running' - elsif all.all?(&:canceled?) + elsif objs.all?(&:canceled?) 'canceled' else 'failed' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index bb0bba77fa22..9a3ebaad604e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -40,19 +40,18 @@ - if project_nav_tab? :builds = nav_link(controller: %w(ci_commits)) do - = link_to project_ci_commits_path(@project), title: 'CI', class: 'shortcuts-ci' do - = icon('cubes fw') + = link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = icon('ship fw') %span - CI + Pipelines %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) - - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = icon('cubes fw') %span Builds - %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) + %span.count.ci_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 671c5abcbed2..8219e7bf7018 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -1,22 +1,14 @@ +- status = commit.status %tr.commit %td.commit-link - - if can?(current_user, :read_commit, commit) - = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do - %strong ##{commit.id} - - else + = link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do + = ci_icon_for_status(status) %strong ##{commit.id} - %td.status - %div - - if can?(current_user, :read_commit, commit) - = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) - - else - = ci_status_with_icon(commit.status) - %td %div - if commit.ref - = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref)   - if commit.tag? %span.label.label-primary tag @@ -35,107 +27,43 @@ %p Cant find HEAD commit for this branch + - stages.each do |stage| + %td + - status = commit.statuses.latest.where(stage: stage).status + - if status + = ci_status_with_icon(status) + - else + = ci_status_with_icon('missing') + %td - %div - Duration: -   - - if commit.started_at && commit.finished_at + - if commit.started_at && commit.finished_at + %p #{duration_in_words(commit.finished_at, commit.started_at)} - - else - \- + - if commit.finished_at %p - Finished: -   - - if commit.finished_at - #{time_ago_with_tooltip(commit.finished_at)} - - else - \- + #{time_ago_with_tooltip(commit.finished_at)} %td.content - .controls.hidden-xs - = link_to project_builds_path(commit.project), class: 'btn btn-grouped btn-xs' do - = icon('cubes fw') - Details + .controls.hidden-xs.pull-right + - artifacts = commit.builds.latest.select { |status| status.artifacts? } + - 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} +   - - @project = commit.project - - ref = 'master' - %span.btn-group.btn-grouped - %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } - %span.caret - %span.sr-only - Select Archive Format - %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.gz - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.bz2 - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar --#%tr.commit --# %td.status --# - if can?(current_user, :read_commit, commit) --# = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) --# - else --# = ci_status_with_icon(commit.status) --# --# %td.commit-link --# - if can?(current_user, :read_commit, commit) --# = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do --# %strong ##{commit.id} --# - else --# %strong ##{commit.id} --# --# - if defined?(commit_sha) && commit_sha --# %td --# = link_to commit.short_sha, namespace_project_commit_path(commit.project.namespace, commit.project, commit.sha), class: "monospace" --# --# %td --# - if commit.ref --# = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) --# - else --# .light none --# --# %td --# = commit.git_commit_message --# --# %td.duration --# - if commit.started_at && commit.finished_at --# #{duration_in_words(commit.finished_at, commit.started_at)} --# --# %td.timestamp --# - if commit.finished_at --# %span #{time_ago_with_tooltip(commit.finished_at)} --# --# - if defined?(coverage) && coverage --# %td.coverage --# - if commit.try(:coverage) --# #{commit.coverage}% --# --# %td --# .pull-right --# - if can?(current_user, :read_commit, commit) && commit.artifacts? --# = link_to download_namespace_project_commit_artifacts_path(commit.project.namespace, commit.project, commit), title: 'Download artifacts' do --# %i.fa.fa-download --# - if can?(current_user, :update_commit, commit) --# - if commit.active? --# = link_to cancel_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Cancel' do --# %i.fa.fa-remove.cred --# - elsif defined?(allow_retry) && allow_retry && commit.retryable? --# = link_to retry_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Retry' do --# %i.fa.fa-repeat --# --#- if commit.yaml_errors.present? --# %tr --# %td{colspan: 7} --# .light --# = commit.yaml_errors \ No newline at end of file + - if can?(current_user, :update_pipeline, @project) + - if commit.retryable? + = link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = icon("repeat") +   + - if commit.active? + = link_to cancel_namespace_project_ci_commit_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/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml index c04fc6be9f1e..27c125ca40f0 100644 --- a/app/views/projects/ci_commits/_header_title.html.haml +++ b/app/views/projects/ci_commits/_header_title.html.haml @@ -1 +1 @@ -- header_title project_title(@project, "CI", project_ci_commits_path(@project)) +- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml index 7955e5e9abfb..0347c2203822 100644 --- a/app/views/projects/ci_commits/index.html.haml +++ b/app/views/projects/ci_commits/index.html.haml @@ -1,11 +1,11 @@ -- page_title "CI Changes" +- page_title "Pipelines" = render "header_title" .top-area %ul.nav-links - %li{class: ('active' if @scope.nil? || @scope == 'latest')} - = link_to project_ci_commits_path(@project, scope: :latest) do - Latest + %li{class: ('active' if @scope.nil?)} + = link_to project_ci_commits_path(@project) do + All %span.badge.js-totalbuilds-count = number_with_delimiter(@all_commits.count(:id)) @@ -21,31 +21,45 @@ %span.badge.js-running-count = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - %li{class: ('active' if @scope == 'failed')} - = link_to project_ci_commits_path(@project, scope: :failed) do + %li{class: ('active' if @scope == 'running')} + = link_to project_ci_commits_path(@project, scope: :running) do Failed %span.badge.js-running-count - = number_with_delimiter(@all_commits.finished.count(:id)) + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) .nav-controls + - if can? current_user, :create_pipeline, @project + = link_to new_namespace_project_ci_commit_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 Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + = 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 - #{(@scope || 'running').capitalize} changes on this project + Pipelines for #{(@scope || 'changes')} on this project %ul.content-list + - stages = @commits.stages - if @commits.blank? %li - .nothing-here-block No commits to show + .nothing-here-block No pipelines to show - else .table-holder %table.table.builds - = render @commits, commit_sha: true, stage: true, allow_retry: true + %tbody + %th Pipeline ID + %th Commit + - @commits.stages.each do |stage| + %th + = stage.titleize + %th + %th + = render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml new file mode 100644 index 000000000000..e9a22bbb1579 --- /dev/null +++ b/app/views/projects/ci_commits/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"} × + = @error +%h3.page-title + New Pipeline +%hr + += form_tag namespace_project_ci_commits_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_ci_commits_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/ci_commits/show.html.haml b/app/views/projects/ci_commits/show.html.haml deleted file mode 100644 index b02aee3db212..000000000000 --- a/app/views/projects/ci_commits/show.html.haml +++ /dev/null @@ -1,216 +0,0 @@ -- page_title "#{@build.name} (##{@build.id})", "Builds" -= render "header_title" - -.build-page - .gray-content-block.top-block - Build ##{@build.id} for commit - %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) - from - = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - - merge_request = @build.merge_request - - if merge_request - via - = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) - - #up-build-trace - - builds = @build.commit.matrix_builds(@build) - - if builds.size > 1 - %ul.nav-links.no-top.no-bottom - - builds.each do |build| - %li{class: ('active' if build == @build) } - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - - if @build.retried? - %li.active - %a - Build ##{@build.id} - · - %i.fa.fa-warning - This build was retried. - - .gray-content-block.middle-block - .build-head - .clearfix - = ci_status_with_icon(@build.status) - - if @build.duration - %span - %i.fa.fa-time - #{duration_in_words(@build.finished_at, @build.started_at)} - .pull-right - #{time_ago_with_tooltip(@build.finished_at) if @build.finished_at} - - - if @build.stuck? - - unless @build.any_runners_online? - .bs-callout.bs-callout-warning - %p - - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have any runners online assigned to it. - - elsif @build.tags.any? - This build is stuck, because you don't have any active runners online with any of these tags assigned to them: - - @build.tags.each do |tag| - %span.label.label-primary - = tag - - else - This build is stuck, because you don't have any active runners that can run this build. - - %br - Go to - = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do - Runners page - - .row.prepend-top-default - .col-md-9 - .clearfix - - if @build.active? - .autoscroll-container - %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll - .clearfix - #js-build-scroll.scroll-controls - = link_to '#up-build-trace', class: 'btn' do - %i.fa.fa-angle-up - = link_to '#down-build-trace', class: 'btn' do - %i.fa.fa-angle-down - - - if @build.erased? - .erased.alert.alert-warning - - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by - Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - - else - %pre.trace#build-trace - %code.bash - = preserve do - = raw @build.trace_html - - %div#down-build-trace - - .col-md-3 - - if @build.coverage - .build-widget - %h4.title - Test coverage - %h1 #{@build.coverage}% - - - if can?(current_user, :read_build, @project) && @build.artifacts? - .build-widget.artifacts - %h4.title Build artifacts - .center - .btn-group{ role: :group } - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do - = icon('download') - Download - - - if @build.artifacts_metadata? - = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do - = icon('folder-open') - Browse - - .build-widget - %h4.title - Build ##{@build.id} - - if can?(current_user, :update_build, @project) - .center - .btn-group{ role: :group } - - if @build.active? - = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post - - elsif @build.retryable? - = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post - - - if @build.erasable? - = link_to erase_namespace_project_build_path(@project.namespace, @project, @build), - class: 'btn btn-sm btn-warning', method: :post, - data: { confirm: 'Are you sure you want to erase this build?' } do - = icon('eraser') - Erase - - .clearfix - - if @build.duration - %p - %span.attr-name Duration: - #{duration_in_words(@build.finished_at, @build.started_at)} - %p - %span.attr-name Created: - #{time_ago_with_tooltip(@build.created_at)} - - if @build.finished_at - %p - %span.attr-name Finished: - #{time_ago_with_tooltip(@build.finished_at)} - - if @build.erased_at - %p - %span.attr-name Erased: - #{time_ago_with_tooltip(@build.erased_at)} - %p - %span.attr-name Runner: - - if @build.runner && current_user && current_user.admin - = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id) - - elsif @build.runner - \##{@build.runner.id} - - - if @build.trigger_request - .build-widget - %h4.title - Trigger - - %p - %span.attr-name Token: - #{@build.trigger_request.trigger.short_token} - - - if @build.trigger_request.variables - %p - %span.attr-name Variables: - - %code - - @build.trigger_request.variables.each do |key, value| - #{key}=#{value} - - .build-widget - %h4.title - Commit - .pull-right - %small - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" - %p - %span.attr-name Branch: - = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - %p - %span.attr-name Author: - #{@build.commit.git_author_name} - %p - %span.attr-name Message: - #{@build.commit.git_commit_message} - - - if @build.tags.any? - .build-widget - %h4.title - Tags - - @build.tag_list.each do |tag| - %span.label.label-primary - = tag - - - if @builds.present? - .build-widget - %h4.title #{pluralize(@builds.count(:id), "other build")} for - = succeed ":" do - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" - %table.table.builds - - @builds.each_with_index do |build, i| - %tr.build - %td - = ci_icon_for_status(build.status) - %td - = link_to namespace_project_build_path(@project.namespace, @project, build) do - - if build.name - = build.name - - else - %span ##{build.id} - - %td.status= build.status - - - :javascript - new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") diff --git a/config/routes.rb b/config/routes.rb index b5d54871cf7e..8e6f7c8d8fa4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -653,10 +653,16 @@ resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] - resources :ci_commits, only: [:index, :show] + resources :ci_commits, only: [:index, :new, :create] do + member do + post :cancel + post :retry + end + end resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do + get :commits post :cancel_all end diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb new file mode 100644 index 000000000000..e9f8eb624d6f --- /dev/null +++ b/db/migrate/20160331204039_add_action_to_ci_commit.rb @@ -0,0 +1,5 @@ +class AddActionToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :action, :string + end +end -- GitLab