Implement Commit Status API
This commit is contained in:
parent
5ffbf5feb7
commit
914cfbd2f1
|
|
@ -16,6 +16,7 @@ v 8.1.0 (unreleased)
|
|||
- Move CI charts to project graphs area
|
||||
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
|
||||
- Add first and last to pagination (Zeger-Jan van de Weg)
|
||||
- Added Commit Status API
|
||||
- Show CI status on commit page
|
||||
- Show CI status on Your projects page and Starred projects page
|
||||
- Remove "Continuous Integration" page from dashboard
|
||||
|
|
|
|||
|
|
@ -135,6 +135,8 @@ class Ability
|
|||
|
||||
def project_report_rules
|
||||
project_guest_rules + [
|
||||
:create_commit_status,
|
||||
:read_commit_statuses,
|
||||
:download_code,
|
||||
:fork_project,
|
||||
:create_project_snippet,
|
||||
|
|
|
|||
|
|
@ -24,32 +24,19 @@
|
|||
#
|
||||
|
||||
module Ci
|
||||
class Build < ActiveRecord::Base
|
||||
extend Ci::Model
|
||||
|
||||
class Build < CommitStatus
|
||||
LAZY_ATTRIBUTES = ['trace']
|
||||
|
||||
belongs_to :commit, class_name: 'Ci::Commit'
|
||||
belongs_to :runner, class_name: 'Ci::Runner'
|
||||
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
|
||||
belongs_to :user
|
||||
|
||||
serialize :options
|
||||
|
||||
validates :commit, presence: true
|
||||
validates :status, presence: true
|
||||
validates :coverage, numericality: true, allow_blank: true
|
||||
validates_presence_of :ref
|
||||
|
||||
scope :running, ->() { where(status: "running") }
|
||||
scope :pending, ->() { where(status: "pending") }
|
||||
scope :success, ->() { where(status: "success") }
|
||||
scope :failed, ->() { where(status: "failed") }
|
||||
scope :unstarted, ->() { where(runner_id: nil) }
|
||||
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
|
||||
scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
|
||||
scope :ignore_failures, ->() { where(allow_failure: false) }
|
||||
scope :for_ref, ->(ref) { where(ref: ref) }
|
||||
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
|
||||
|
||||
acts_as_taggable
|
||||
|
|
@ -74,13 +61,14 @@ module Ci
|
|||
|
||||
def create_from(build)
|
||||
new_build = build.dup
|
||||
new_build.status = :pending
|
||||
new_build.status = 'pending'
|
||||
new_build.runner_id = nil
|
||||
new_build.trigger_request_id = nil
|
||||
new_build.save
|
||||
end
|
||||
|
||||
def retry(build)
|
||||
new_build = Ci::Build.new(status: :pending)
|
||||
new_build = Ci::Build.new(status: 'pending')
|
||||
new_build.ref = build.ref
|
||||
new_build.tag = build.tag
|
||||
new_build.options = build.options
|
||||
|
|
@ -98,28 +86,7 @@ module Ci
|
|||
end
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
event :run do
|
||||
transition pending: :running
|
||||
end
|
||||
|
||||
event :drop do
|
||||
transition running: :failed
|
||||
end
|
||||
|
||||
event :success do
|
||||
transition running: :success
|
||||
end
|
||||
|
||||
event :cancel do
|
||||
transition [:pending, :running] => :canceled
|
||||
end
|
||||
|
||||
after_transition pending: :running do |build, transition|
|
||||
build.update_attributes started_at: Time.now
|
||||
end
|
||||
|
||||
after_transition any => [:success, :failed, :canceled] do |build, transition|
|
||||
build.update_attributes finished_at: Time.now
|
||||
project = build.project
|
||||
|
||||
if project.web_hooks?
|
||||
|
|
@ -136,19 +103,10 @@ module Ci
|
|||
build.update_coverage
|
||||
end
|
||||
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, :project, :gl_project,
|
||||
to: :commit, prefix: false
|
||||
|
||||
def before_sha
|
||||
Gitlab::Git::BLANK_SHA
|
||||
def ignored?
|
||||
failed? && allow_failure?
|
||||
end
|
||||
|
||||
def trace_html
|
||||
|
|
@ -156,22 +114,6 @@ module Ci
|
|||
html || ''
|
||||
end
|
||||
|
||||
def started?
|
||||
!pending? && !canceled? && started_at
|
||||
end
|
||||
|
||||
def active?
|
||||
running? || pending?
|
||||
end
|
||||
|
||||
def complete?
|
||||
canceled? || success? || failed?
|
||||
end
|
||||
|
||||
def ignored?
|
||||
failed? && allow_failure?
|
||||
end
|
||||
|
||||
def timeout
|
||||
project.timeout
|
||||
end
|
||||
|
|
@ -180,14 +122,6 @@ module Ci
|
|||
yaml_variables + project_variables + trigger_variables
|
||||
end
|
||||
|
||||
def duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.now - started_at
|
||||
end
|
||||
end
|
||||
|
||||
def project
|
||||
commit.project
|
||||
end
|
||||
|
|
@ -278,6 +212,15 @@ module Ci
|
|||
"#{dir_to_trace}/#{id}.log"
|
||||
end
|
||||
|
||||
def description
|
||||
name
|
||||
end
|
||||
|
||||
def target_url
|
||||
Gitlab::Application.routes.url_helpers.
|
||||
namespace_project_build_url(gl_project.namespace, gl_project, self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def yaml_variables
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ module Ci
|
|||
extend Ci::Model
|
||||
|
||||
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
|
||||
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
|
||||
has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
|
||||
has_many :builds, class_name: 'Ci::Build'
|
||||
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
|
||||
|
||||
validates_presence_of :sha
|
||||
|
|
@ -81,12 +82,11 @@ module Ci
|
|||
end
|
||||
|
||||
def stage
|
||||
running_or_pending = builds_without_retry.running_or_pending
|
||||
running_or_pending.limit(1).pluck(:stage).first
|
||||
running_or_pending = statuses.latest.running_or_pending
|
||||
running_or_pending.first.try(:stage)
|
||||
end
|
||||
|
||||
def create_builds(ref, tag, user, trigger_request = nil)
|
||||
return if skip_ci? && trigger_request.blank?
|
||||
return unless config_processor
|
||||
config_processor.stages.any? do |stage|
|
||||
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
|
||||
|
|
@ -94,7 +94,6 @@ module Ci
|
|||
end
|
||||
|
||||
def create_next_builds(ref, tag, user, trigger_request)
|
||||
return if skip_ci? && trigger_request.blank?
|
||||
return unless config_processor
|
||||
|
||||
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
|
||||
|
|
@ -107,39 +106,47 @@ module Ci
|
|||
end
|
||||
|
||||
def refs
|
||||
builds.group(:ref).pluck(:ref)
|
||||
statuses.pluck(:ref).compact.uniq
|
||||
end
|
||||
|
||||
def last_ref
|
||||
builds.latest.first.try(:ref)
|
||||
def statuses_for_ref(ref = nil)
|
||||
if ref
|
||||
statuses.for_ref(ref)
|
||||
else
|
||||
statuses
|
||||
end
|
||||
end
|
||||
|
||||
def builds_without_retry
|
||||
builds.latest
|
||||
def builds_without_retry(ref = nil)
|
||||
if ref
|
||||
builds.for_ref(ref).latest
|
||||
else
|
||||
builds.latest
|
||||
end
|
||||
end
|
||||
|
||||
def builds_without_retry_for_ref(ref)
|
||||
builds.for_ref(ref).latest
|
||||
def retried
|
||||
@retried ||= (statuses.order(id: :desc) - statuses.latest)
|
||||
end
|
||||
|
||||
def retried_builds
|
||||
@retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
|
||||
end
|
||||
|
||||
def status
|
||||
if skip_ci?
|
||||
return 'skipped'
|
||||
elsif yaml_errors.present?
|
||||
def status(ref = nil)
|
||||
if yaml_errors.present?
|
||||
return 'failed'
|
||||
elsif builds.none?
|
||||
end
|
||||
|
||||
latest_statuses = statuses.latest.to_a
|
||||
latest_statuses.reject! { |status| status.try(&:allow_failure?) }
|
||||
latest_statuses.select! { |status| status.ref == nil || status.ref == ref } if ref
|
||||
|
||||
if latest_statuses.none?
|
||||
return 'skipped'
|
||||
elsif success?
|
||||
elsif latest_statuses.all?(&:success?)
|
||||
'success'
|
||||
elsif pending?
|
||||
elsif latest_statuses.all?(&:pending?)
|
||||
'pending'
|
||||
elsif running?
|
||||
elsif latest_statuses.any?(&:running?) || latest_statuses.any?(&:pending?)
|
||||
'running'
|
||||
elsif canceled?
|
||||
elsif latest_statuses.all?(&:canceled?)
|
||||
'canceled'
|
||||
else
|
||||
'failed'
|
||||
|
|
@ -147,21 +154,15 @@ module Ci
|
|||
end
|
||||
|
||||
def pending?
|
||||
builds_without_retry.all? do |build|
|
||||
build.pending?
|
||||
end
|
||||
status == 'pending'
|
||||
end
|
||||
|
||||
def running?
|
||||
builds_without_retry.any? do |build|
|
||||
build.running? || build.pending?
|
||||
end
|
||||
status == 'running'
|
||||
end
|
||||
|
||||
def success?
|
||||
builds_without_retry.all? do |build|
|
||||
build.success? || build.ignored?
|
||||
end
|
||||
status == 'success'
|
||||
end
|
||||
|
||||
def failed?
|
||||
|
|
@ -169,21 +170,15 @@ module Ci
|
|||
end
|
||||
|
||||
def canceled?
|
||||
builds_without_retry.all? do |build|
|
||||
build.canceled?
|
||||
end
|
||||
status == 'canceled'
|
||||
end
|
||||
|
||||
def duration
|
||||
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
|
||||
end
|
||||
|
||||
def duration_for_ref(ref)
|
||||
builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
|
||||
def duration(ref = nil)
|
||||
statuses_for_ref(ref).latest.select(&:duration).sum(&:duration).to_i
|
||||
end
|
||||
|
||||
def finished_at
|
||||
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
|
||||
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
|
||||
end
|
||||
|
||||
def coverage
|
||||
|
|
@ -195,8 +190,8 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def matrix_for_ref?(ref)
|
||||
builds_without_retry_for_ref(ref).pluck(:id).size > 1
|
||||
def matrix?(ref)
|
||||
builds_without_retry(ref).pluck(:id).size > 1
|
||||
end
|
||||
|
||||
def config_processor
|
||||
|
|
@ -217,7 +212,6 @@ module Ci
|
|||
end
|
||||
|
||||
def skip_ci?
|
||||
return false if builds.any?
|
||||
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -184,4 +184,12 @@ class Commit
|
|||
def parents
|
||||
@parents ||= Commit.decorate(super, project)
|
||||
end
|
||||
|
||||
def ci_commit
|
||||
project.ci_commit(sha)
|
||||
end
|
||||
|
||||
def status
|
||||
ci_commit.try(:status) || :not_found
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
class CommitStatus < ActiveRecord::Base
|
||||
self.table_name = 'ci_builds'
|
||||
|
||||
belongs_to :commit, class_name: 'Ci::Commit'
|
||||
belongs_to :user
|
||||
|
||||
validates :commit, presence: true
|
||||
validates :status, inclusion: {in: %w(pending running failed success canceled)}
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
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 :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
|
||||
scope :for_ref, ->(ref) { where(ref: [ref, nil]) }
|
||||
scope :running_or_pending, ->() { where(status: [:running, :pending]) }
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
event :run do
|
||||
transition pending: :running
|
||||
end
|
||||
|
||||
event :drop do
|
||||
transition running: :failed
|
||||
end
|
||||
|
||||
event :success do
|
||||
transition [:pending, :running] => :success
|
||||
end
|
||||
|
||||
event :cancel do
|
||||
transition [:pending, :running] => :canceled
|
||||
end
|
||||
|
||||
after_transition pending: :running do |build, transition|
|
||||
build.update_attributes started_at: Time.now
|
||||
end
|
||||
|
||||
after_transition any => [:success, :failed, :canceled] do |build, transition|
|
||||
build.update_attributes finished_at: Time.now
|
||||
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, :gl_project,
|
||||
to: :commit, prefix: false
|
||||
|
||||
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
|
||||
|
||||
def duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.now - started_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
class GenericCommitStatus < CommitStatus
|
||||
before_validation :set_default_values
|
||||
|
||||
# GitHub compatible API
|
||||
alias_attribute :context, :name
|
||||
|
||||
def set_default_values
|
||||
self.context ||= 'default'
|
||||
self.stage ||= 'external'
|
||||
end
|
||||
|
||||
def tags
|
||||
[:external]
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,8 @@ module Ci
|
|||
|
||||
tag = origin_ref.start_with?('refs/tags/')
|
||||
commit = project.gl_project.ensure_ci_commit(sha)
|
||||
return false if commit.skip_ci?
|
||||
|
||||
commit.update_committed!
|
||||
commit.create_builds(ref, tag, user)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
- gl_project = build.project.gl_project
|
||||
%tr.build
|
||||
%td.status
|
||||
= ci_status_with_icon(build.status)
|
||||
|
||||
%td.build-link
|
||||
= link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
|
||||
%strong Build ##{build.id}
|
||||
|
||||
- if defined?(ref)
|
||||
%td
|
||||
= build.ref
|
||||
|
||||
%td
|
||||
= build.stage
|
||||
|
||||
%td
|
||||
= build.name
|
||||
.pull-right
|
||||
- if build.tags.any?
|
||||
- build.tag_list.each do |tag|
|
||||
%span.label.label-primary
|
||||
= tag
|
||||
- if build.trigger_request
|
||||
%span.label.label-info triggered
|
||||
- if build.allow_failure
|
||||
%span.label.label-danger allowed to fail
|
||||
|
||||
%td.duration
|
||||
- if build.duration
|
||||
#{duration_in_words(build.finished_at, build.started_at)}
|
||||
|
||||
%td.timestamp
|
||||
- if build.finished_at
|
||||
%span #{time_ago_in_words build.finished_at} ago
|
||||
|
||||
- if build.project.coverage_enabled?
|
||||
%td.coverage
|
||||
- if build.coverage
|
||||
#{build.coverage}%
|
||||
|
||||
%td
|
||||
- if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
|
||||
.pull-right
|
||||
- if build.active?
|
||||
= link_to cancel_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), title: 'Cancel build' do
|
||||
%i.fa.fa-remove.cred
|
||||
- elsif build.commands.present?
|
||||
= link_to retry_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), method: :post, title: 'Retry build' do
|
||||
%i.fa.fa-repeat
|
||||
|
|
@ -7,9 +7,9 @@
|
|||
%code #{@build.ref}
|
||||
|
||||
#up-build-trace
|
||||
- if @commit.matrix_for_ref?(@build.ref)
|
||||
- if @commit.matrix?(@build.ref)
|
||||
%ul.center-top-menu.build-top-menu
|
||||
- @commit.builds_without_retry_for_ref(@build.ref).each do |build|
|
||||
- @commit.builds_without_retry(@build.ref).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)
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
= build.id
|
||||
|
||||
|
||||
- unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
|
||||
- unless @commit.builds_without_retry(@build.ref).include?(@build)
|
||||
%li.active
|
||||
%a
|
||||
Build ##{@build.id}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@
|
|||
.bs-callout.bs-callout-warning
|
||||
\.gitlab-ci.yml not found in this commit
|
||||
|
||||
- @ci_commit.refs.each do |ref|
|
||||
- if @ci_commit.refs.blank?
|
||||
.gray-content-block.second-block
|
||||
Builds for #{ref}
|
||||
- if @ci_commit.duration_for_ref(ref) > 0
|
||||
Latest builds
|
||||
- if @ci_commit.duration > 0
|
||||
%small.pull-right
|
||||
%i.fa.fa-time
|
||||
#{time_interval_in_words @ci_commit.duration_for_ref(ref)}
|
||||
#{time_interval_in_words @ci_commit.duration}
|
||||
|
||||
%table.table.builds
|
||||
%thead
|
||||
|
|
@ -40,10 +40,32 @@
|
|||
- if @ci_project && @ci_project.coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
= render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true
|
||||
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.latest, coverage: @ci_project.try(:coverage_enabled?), controls: true
|
||||
|
||||
- if @ci_commit.retried_builds.any?
|
||||
%h3
|
||||
- @ci_commit.refs.sort.each do |ref|
|
||||
.gray-content-block.second-block
|
||||
Builds for #{ref}
|
||||
- if @ci_commit.duration(ref) > 0
|
||||
%small.pull-right
|
||||
%i.fa.fa-time
|
||||
#{time_interval_in_words @ci_commit.duration(ref)}
|
||||
|
||||
%table.table.builds
|
||||
%thead
|
||||
%tr
|
||||
%th Status
|
||||
%th Build ID
|
||||
%th Stage
|
||||
%th Name
|
||||
%th Duration
|
||||
%th Finished at
|
||||
- if @ci_project && @ci_project.coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest, coverage: @ci_project.try(:coverage_enabled?), controls: true
|
||||
|
||||
- if @ci_commit.retried.any?
|
||||
.gray-content-block.second-block
|
||||
Retried builds
|
||||
|
||||
%table.table.builds
|
||||
|
|
@ -59,4 +81,4 @@
|
|||
- if @ci_project && @ci_project.coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
= render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true
|
||||
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?), ref: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
%tr.commit_status
|
||||
%td.status
|
||||
= ci_status_with_icon(commit_status.status)
|
||||
|
||||
%td.commit_status-link
|
||||
- if commit_status.target_url
|
||||
= link_to commit_status.target_url do
|
||||
%strong Build ##{commit_status.id}
|
||||
- else
|
||||
%strong Build ##{commit_status.id}
|
||||
|
||||
- if defined?(ref)
|
||||
%td
|
||||
= commit_status.ref
|
||||
|
||||
%td
|
||||
= commit_status.stage
|
||||
|
||||
%td
|
||||
= commit_status.description
|
||||
.pull-right
|
||||
- if commit_status.tags.any?
|
||||
- commit_status.tags.each do |tag|
|
||||
%span.label.label-primary
|
||||
= tag
|
||||
- if commit_status.try(:trigger_request)
|
||||
%span.label.label-info triggered
|
||||
- if commit_status.try(:allow_failure)
|
||||
%span.label.label-danger allowed to fail
|
||||
|
||||
%td.duration
|
||||
- if commit_status.duration
|
||||
#{duration_in_words(commit_status.finished_at, commit_status.started_at)}
|
||||
|
||||
%td.timestamp
|
||||
- if commit_status.finished_at
|
||||
%span #{time_ago_in_words commit_status.finished_at} ago
|
||||
|
||||
- if defined?(coverage)
|
||||
%td.coverage
|
||||
- if commit_status.try(:coverage)
|
||||
#{commit_status.coverage}%
|
||||
|
||||
%td
|
||||
- if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
|
||||
.pull-right
|
||||
- if commit_status.active?
|
||||
= link_to cancel_namespace_project_build_path(gl_project.namespace, gl_project, commit_status, return_to: request.original_url), title: 'Cancel commit_status' do
|
||||
%i.fa.fa-remove.cred
|
||||
- elsif commit_status.commands.present?
|
||||
= link_to retry_namespace_project_build_path(gl_project.namespace, gl_project, commit_status, return_to: request.original_url), method: :post, title: 'Retry commit_status' do
|
||||
%i.fa.fa-repeat
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class AddTypeAndDescriptionToBuilds < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :ci_builds, :type, :string
|
||||
add_column :ci_builds, :target_url, :string
|
||||
add_column :ci_builds, :description, :string
|
||||
add_index :ci_builds, [:commit_id, :type, :ref]
|
||||
add_index :ci_builds, [:commit_id, :type, :name, :ref]
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class MigrateNameToDescriptionForBuilds < ActiveRecord::Migration
|
||||
def change
|
||||
execute("UPDATE ci_builds SET type='Ci::Build' WHERE type IS NULL")
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20151007120511) do
|
||||
ActiveRecord::Schema.define(version: 20151008130321) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
|
@ -103,9 +103,14 @@ ActiveRecord::Schema.define(version: 20151007120511) do
|
|||
t.boolean "tag"
|
||||
t.string "ref"
|
||||
t.integer "user_id"
|
||||
t.string "type"
|
||||
t.string "target_url"
|
||||
t.string "description"
|
||||
end
|
||||
|
||||
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
|
||||
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
|
||||
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
|
||||
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
|
||||
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
|
||||
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ Parameters:
|
|||
"authored_date": "2012-09-20T09:06:12+03:00",
|
||||
"parent_ids": [
|
||||
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
|
||||
]
|
||||
],
|
||||
"status": "running"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ module API
|
|||
mount Services
|
||||
mount Files
|
||||
mount Commits
|
||||
mount CommitStatus
|
||||
mount Namespaces
|
||||
mount Branches
|
||||
mount Labels
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
require 'mime/types'
|
||||
|
||||
module API
|
||||
# Project commit statuses API
|
||||
class CommitStatus < Grape::API
|
||||
resource :projects do
|
||||
before { authenticate! }
|
||||
before { authorize! :read_commit_statuses, user_project }
|
||||
|
||||
# Get a commit's statuses
|
||||
#
|
||||
# Parameters:
|
||||
# id (required) - The ID of a project
|
||||
# sha (required) - The commit hash
|
||||
# ref (optional) - The ref
|
||||
# stage (optional) - The stage
|
||||
# name (optional) - The name
|
||||
# all (optional) - Show all statuses, default: false
|
||||
# Examples:
|
||||
# GET /projects/:id/repository/commits/:sha/statuses
|
||||
get ':id/repository/commits/:sha/statuses' do
|
||||
sha = params[:sha]
|
||||
ci_commit = user_project.ci_commit(sha)
|
||||
not_found! 'Commit' unless ci_commit
|
||||
statuses = ci_commit.statuses
|
||||
statuses = statuses.latest unless parse_boolean(params[:all])
|
||||
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
|
||||
statuses = statuses.where(name: params[:stage]) if params[:stage].present?
|
||||
statuses = statuses.where(name: params[:name]) if params[:name].present?
|
||||
present paginate(statuses), with: Entities::CommitStatus
|
||||
end
|
||||
|
||||
# Post status to commit
|
||||
#
|
||||
# Parameters:
|
||||
# id (required) - The ID of a project
|
||||
# sha (required) - The commit hash
|
||||
# ref (optional) - The ref
|
||||
# state (required) - The state of the status. Can be: pending, running, success, error or failure
|
||||
# target_url (optional) - The target URL to associate with this status
|
||||
# description (optional) - A short description of the status
|
||||
# name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default"
|
||||
# Examples:
|
||||
# POST /projects/:id/repository/commits/:sha/status
|
||||
post ':id/statuses/:sha' do
|
||||
required_attributes! [:state]
|
||||
attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name]
|
||||
commit = @project.commit(params[:sha])
|
||||
not_found! 'Commit' unless commit
|
||||
|
||||
ci_commit = @project.ensure_ci_commit(commit.sha)
|
||||
|
||||
name = params[:name] || params[:context]
|
||||
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
|
||||
status = GenericCommitStatus.new(commit: ci_commit) unless status
|
||||
status.update(attrs)
|
||||
|
||||
case params[:state].to_s
|
||||
when 'running'
|
||||
status.run
|
||||
when 'success'
|
||||
status.success
|
||||
when 'failed'
|
||||
status.drop
|
||||
when 'canceled'
|
||||
status.cancel
|
||||
else
|
||||
status.status = params[:state].to_s
|
||||
end
|
||||
|
||||
if status.save
|
||||
present status, with: Entities::CommitStatus
|
||||
else
|
||||
render_validation_error!(status)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -65,7 +65,7 @@ module API
|
|||
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
|
||||
expose :creator_id
|
||||
expose :namespace
|
||||
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
|
||||
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda { |project, options| project.forked? }
|
||||
expose :avatar_url
|
||||
expose :star_count, :forks_count
|
||||
end
|
||||
|
|
@ -149,6 +149,7 @@ module API
|
|||
|
||||
class RepoCommitDetail < RepoCommit
|
||||
expose :parent_ids, :committed_date, :authored_date
|
||||
expose :status
|
||||
end
|
||||
|
||||
class ProjectSnippet < Grape::Entity
|
||||
|
|
@ -228,6 +229,11 @@ module API
|
|||
expose :created_at
|
||||
end
|
||||
|
||||
class CommitStatus < Grape::Entity
|
||||
expose :id, :sha, :ref, :status, :name, :target_url, :description,
|
||||
:created_at, :started_at, :finished_at
|
||||
end
|
||||
|
||||
class Event < Grape::Entity
|
||||
expose :title, :project_id, :action_name
|
||||
expose :target_id, :target_type, :author_id
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ module Ci
|
|||
module API
|
||||
module Entities
|
||||
class Commit < Grape::Entity
|
||||
expose :id, :ref, :sha, :project_id, :before_sha, :created_at
|
||||
expose :id, :sha, :project_id, :created_at
|
||||
expose :status, :finished_at, :duration
|
||||
expose :git_commit_message, :git_author_name, :git_author_email
|
||||
end
|
||||
|
|
@ -12,7 +12,7 @@ module Ci
|
|||
end
|
||||
|
||||
class Build < Grape::Entity
|
||||
expose :id, :commands, :ref, :sha, :project_id, :repo_url,
|
||||
expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url,
|
||||
:before_sha, :allow_git_fetch, :project_name
|
||||
|
||||
expose :options do |model|
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
FactoryGirl.define do
|
||||
factory :ci_build, class: Ci::Build do
|
||||
name 'test'
|
||||
ref 'master'
|
||||
tag false
|
||||
started_at 'Di 29. Okt 09:51:28 CET 2013'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
FactoryGirl.define do
|
||||
factory :commit_status, class: CommitStatus do
|
||||
started_at 'Di 29. Okt 09:51:28 CET 2013'
|
||||
finished_at 'Di 29. Okt 09:53:28 CET 2013'
|
||||
name 'default'
|
||||
status 'success'
|
||||
description 'commit status'
|
||||
commit factory: :ci_commit
|
||||
|
||||
factory :generic_commit_status, class: GenericCommitStatus do
|
||||
name 'generic'
|
||||
description 'external commit status'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,6 +12,7 @@ describe "Commits" do
|
|||
@ci_project = project.ensure_gitlab_ci_project
|
||||
@commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha
|
||||
@build = FactoryGirl.create :ci_build, commit: @commit
|
||||
@generic_status = FactoryGirl.create :generic_commit_status, commit: @commit
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -30,17 +30,9 @@ describe Ci::Build do
|
|||
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
|
||||
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
|
||||
let(:build) { FactoryGirl.create :ci_build, commit: commit }
|
||||
subject { build }
|
||||
|
||||
it { is_expected.to belong_to(:commit) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to validate_presence_of :status }
|
||||
it { is_expected.to validate_presence_of :ref }
|
||||
|
||||
it { is_expected.to respond_to :success? }
|
||||
it { is_expected.to respond_to :failed? }
|
||||
it { is_expected.to respond_to :running? }
|
||||
it { is_expected.to respond_to :pending? }
|
||||
it { is_expected.to respond_to :trace_html }
|
||||
|
||||
describe :first_pending do
|
||||
|
|
@ -67,72 +59,6 @@ describe Ci::Build do
|
|||
end
|
||||
end
|
||||
|
||||
describe :started? do
|
||||
subject { build.started? }
|
||||
|
||||
context 'without started_at' do
|
||||
before { build.started_at = nil }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
%w(running success failed).each do |status|
|
||||
context "if build status is #{status}" do
|
||||
before { build.status = status }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(pending canceled).each do |status|
|
||||
context "if build status is #{status}" do
|
||||
before { build.status = status }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :active? do
|
||||
subject { build.active? }
|
||||
|
||||
%w(pending running).each do |state|
|
||||
context "if build.status is #{state}" do
|
||||
before { build.status = state }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(success failed canceled).each do |state|
|
||||
context "if build.status is #{state}" do
|
||||
before { build.status = state }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :complete? do
|
||||
subject { build.complete? }
|
||||
|
||||
%w(success failed canceled).each do |state|
|
||||
context "if build.status is #{state}" do
|
||||
before { build.status = state }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(pending running).each do |state|
|
||||
context "if build.status is #{state}" do
|
||||
before { build.status = state }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :ignored? do
|
||||
subject { build.ignored? }
|
||||
|
||||
|
|
@ -200,31 +126,6 @@ describe Ci::Build do
|
|||
it { is_expected.to eq(commit.project.timeout) }
|
||||
end
|
||||
|
||||
describe :duration do
|
||||
subject { build.duration }
|
||||
|
||||
it { is_expected.to eq(120.0) }
|
||||
|
||||
context 'if the building process has not started yet' do
|
||||
before do
|
||||
build.started_at = nil
|
||||
build.finished_at = nil
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'if the building process has started' do
|
||||
before do
|
||||
build.started_at = Time.now - 1.minute
|
||||
build.finished_at = nil
|
||||
end
|
||||
|
||||
it { is_expected.to be_a(Float) }
|
||||
it { is_expected.to be > 0.0 }
|
||||
end
|
||||
end
|
||||
|
||||
describe :options do
|
||||
let(:options) do
|
||||
{
|
||||
|
|
@ -239,18 +140,6 @@ describe Ci::Build do
|
|||
it { is_expected.to eq(options) }
|
||||
end
|
||||
|
||||
describe :sha do
|
||||
subject { build.sha }
|
||||
|
||||
it { is_expected.to eq(commit.sha) }
|
||||
end
|
||||
|
||||
describe :short_sha do
|
||||
subject { build.short_sha }
|
||||
|
||||
it { is_expected.to eq(commit.short_sha) }
|
||||
end
|
||||
|
||||
describe :allow_git_fetch do
|
||||
subject { build.allow_git_fetch }
|
||||
|
||||
|
|
@ -23,6 +23,8 @@ describe Ci::Commit do
|
|||
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
|
||||
|
||||
it { is_expected.to belong_to(:gl_project) }
|
||||
it { is_expected.to have_many(:statuses) }
|
||||
it { is_expected.to have_many(:trigger_requests) }
|
||||
it { is_expected.to have_many(:builds) }
|
||||
it { is_expected.to validate_presence_of :sha }
|
||||
|
||||
|
|
@ -47,10 +49,12 @@ describe Ci::Commit do
|
|||
@second = FactoryGirl.create :ci_build, commit: commit
|
||||
end
|
||||
|
||||
it "creates new build" do
|
||||
it "creates only a new build" do
|
||||
expect(commit.builds.count(:all)).to eq 2
|
||||
expect(commit.statuses.count(:all)).to eq 2
|
||||
commit.retry
|
||||
expect(commit.builds.count(:all)).to eq 3
|
||||
expect(commit.statuses.count(:all)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -78,8 +82,8 @@ describe Ci::Commit do
|
|||
subject { commit.stage }
|
||||
|
||||
before do
|
||||
@second = FactoryGirl.create :ci_build, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: :pending
|
||||
@first = FactoryGirl.create :ci_build, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: :pending
|
||||
@second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
|
||||
@first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
|
||||
end
|
||||
|
||||
it 'returns first running stage' do
|
||||
|
|
@ -88,7 +92,7 @@ describe Ci::Commit do
|
|||
|
||||
context 'first build succeeded' do
|
||||
before do
|
||||
@first.update_attributes(status: :success)
|
||||
@first.success
|
||||
end
|
||||
|
||||
it 'returns last running stage' do
|
||||
|
|
@ -98,8 +102,8 @@ describe Ci::Commit do
|
|||
|
||||
context 'all builds succeeded' do
|
||||
before do
|
||||
@first.update_attributes(status: :success)
|
||||
@second.update_attributes(status: :success)
|
||||
@first.success
|
||||
@second.success
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
|
|
@ -111,6 +115,33 @@ describe Ci::Commit do
|
|||
describe :create_next_builds do
|
||||
end
|
||||
|
||||
describe :refs do
|
||||
subject { commit.refs }
|
||||
|
||||
before do
|
||||
FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
|
||||
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
|
||||
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
|
||||
end
|
||||
|
||||
it 'returns all refs' do
|
||||
is_expected.to contain_exactly('master', 'develop')
|
||||
end
|
||||
end
|
||||
|
||||
describe :retried do
|
||||
subject { commit.retried }
|
||||
|
||||
before do
|
||||
@commit1 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
|
||||
@commit2 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
|
||||
end
|
||||
|
||||
it 'returns old builds' do
|
||||
is_expected.to contain_exactly(@commit1)
|
||||
end
|
||||
end
|
||||
|
||||
describe :create_builds do
|
||||
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
|
||||
|
||||
|
|
@ -252,10 +283,10 @@ describe Ci::Commit do
|
|||
|
||||
describe :should_create_next_builds? do
|
||||
before do
|
||||
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: :success
|
||||
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: :failed
|
||||
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: :failed
|
||||
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :success
|
||||
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success'
|
||||
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed'
|
||||
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed'
|
||||
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
|
||||
end
|
||||
|
||||
context 'for success' do
|
||||
|
|
@ -266,7 +297,7 @@ describe Ci::Commit do
|
|||
|
||||
context 'for failed' do
|
||||
before do
|
||||
@build4.update_attributes(status: :failed)
|
||||
@build4.update_attributes(status: 'failed')
|
||||
end
|
||||
|
||||
it 'to not create' do
|
||||
|
|
@ -286,7 +317,7 @@ describe Ci::Commit do
|
|||
|
||||
context 'for running' do
|
||||
before do
|
||||
@build4.update_attributes(status: :running)
|
||||
@build4.update_attributes(status: 'running')
|
||||
end
|
||||
|
||||
it 'to not create' do
|
||||
|
|
@ -296,7 +327,7 @@ describe Ci::Commit do
|
|||
|
||||
context 'for retried' do
|
||||
before do
|
||||
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :failed
|
||||
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed'
|
||||
end
|
||||
|
||||
it 'to not create' do
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ describe Ci::MailService do
|
|||
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
|
||||
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
|
||||
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
|
||||
|
||||
before do
|
||||
allow(mail).to receive_messages(
|
||||
|
|
@ -86,7 +86,7 @@ describe Ci::MailService do
|
|||
end
|
||||
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
|
||||
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
|
||||
|
||||
before do
|
||||
allow(mail).to receive_messages(
|
||||
|
|
@ -115,7 +115,7 @@ describe Ci::MailService do
|
|||
end
|
||||
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
|
||||
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
|
||||
|
||||
before do
|
||||
allow(mail).to receive_messages(
|
||||
|
|
@ -144,7 +144,7 @@ describe Ci::MailService do
|
|||
end
|
||||
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
|
||||
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
|
||||
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
|
||||
|
||||
before do
|
||||
allow(mail).to receive_messages(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe CommitStatus do
|
||||
let(:commit) { FactoryGirl.create :ci_commit }
|
||||
let(:commit_status) { FactoryGirl.create :commit_status, commit: commit }
|
||||
|
||||
it { is_expected.to belong_to(:commit) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }
|
||||
|
||||
it { is_expected.to delegate_method(:sha).to(:commit) }
|
||||
it { is_expected.to delegate_method(:short_sha).to(:commit) }
|
||||
it { is_expected.to delegate_method(:gl_project).to(:commit) }
|
||||
|
||||
it { is_expected.to respond_to :success? }
|
||||
it { is_expected.to respond_to :failed? }
|
||||
it { is_expected.to respond_to :running? }
|
||||
it { is_expected.to respond_to :pending? }
|
||||
|
||||
describe :started? do
|
||||
subject { commit_status.started? }
|
||||
|
||||
context 'without started_at' do
|
||||
before { commit_status.started_at = nil }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
%w(running success failed).each do |status|
|
||||
context "if commit status is #{status}" do
|
||||
before { commit_status.status = status }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(pending canceled).each do |status|
|
||||
context "if commit status is #{status}" do
|
||||
before { commit_status.status = status }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :active? do
|
||||
subject { commit_status.active? }
|
||||
|
||||
%w(pending running).each do |state|
|
||||
context "if commit_status.status is #{state}" do
|
||||
before { commit_status.status = state }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(success failed canceled).each do |state|
|
||||
context "if commit_status.status is #{state}" do
|
||||
before { commit_status.status = state }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :complete? do
|
||||
subject { commit_status.complete? }
|
||||
|
||||
%w(success failed canceled).each do |state|
|
||||
context "if commit_status.status is #{state}" do
|
||||
before { commit_status.status = state }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
%w(pending running).each do |state|
|
||||
context "if commit_status.status is #{state}" do
|
||||
before { commit_status.status = state }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :duration do
|
||||
subject { commit_status.duration }
|
||||
|
||||
it { is_expected.to eq(120.0) }
|
||||
|
||||
context 'if the building process has not started yet' do
|
||||
before do
|
||||
commit_status.started_at = nil
|
||||
commit_status.finished_at = nil
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'if the building process has started' do
|
||||
before do
|
||||
commit_status.started_at = Time.now - 1.minute
|
||||
commit_status.finished_at = nil
|
||||
end
|
||||
|
||||
it { is_expected.to be_a(Float) }
|
||||
it { is_expected.to be > 0.0 }
|
||||
end
|
||||
end
|
||||
|
||||
describe :latest do
|
||||
subject { CommitStatus.latest.order(:id) }
|
||||
|
||||
before do
|
||||
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
|
||||
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
|
||||
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'cc', status: 'success'
|
||||
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'bb', status: 'success'
|
||||
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'success'
|
||||
end
|
||||
|
||||
it 'return unique statuses' do
|
||||
is_expected.to eq([@commit2, @commit3, @commit4, @commit5])
|
||||
end
|
||||
end
|
||||
|
||||
describe :for_ref do
|
||||
subject { CommitStatus.for_ref('bb').order(:id) }
|
||||
|
||||
before do
|
||||
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
|
||||
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
|
||||
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
|
||||
end
|
||||
|
||||
it 'return statuses with equal and nil ref set' do
|
||||
is_expected.to eq([@commit1, @commit3])
|
||||
end
|
||||
end
|
||||
|
||||
describe :running_or_pending do
|
||||
subject { CommitStatus.running_or_pending.order(:id) }
|
||||
|
||||
before do
|
||||
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
|
||||
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
|
||||
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
|
||||
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
|
||||
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
|
||||
end
|
||||
|
||||
it 'return statuses that are running or pending' do
|
||||
is_expected.to eq([@commit1, @commit2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe GenericCommitStatus do
|
||||
let(:commit) { FactoryGirl.create :ci_commit }
|
||||
let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit }
|
||||
|
||||
describe :context do
|
||||
subject { generic_commit_status.context }
|
||||
before { generic_commit_status.context = 'my_context' }
|
||||
|
||||
it { is_expected.to eq(generic_commit_status.name) }
|
||||
end
|
||||
|
||||
describe :tags do
|
||||
subject { generic_commit_status.tags }
|
||||
|
||||
it { is_expected.to eq([:external]) }
|
||||
end
|
||||
|
||||
describe :set_default_values do
|
||||
before do
|
||||
generic_commit_status.context = nil
|
||||
generic_commit_status.stage = nil
|
||||
generic_commit_status.save
|
||||
end
|
||||
|
||||
describe :context do
|
||||
subject { generic_commit_status.context }
|
||||
|
||||
it { is_expected.to_not be_nil }
|
||||
end
|
||||
|
||||
describe :stage do
|
||||
subject { generic_commit_status.stage }
|
||||
|
||||
it { is_expected.to_not be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe API::API, api: true do
|
||||
include ApiHelpers
|
||||
let(:user) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let!(:project) { create(:project, creator_id: user.id) }
|
||||
let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
|
||||
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
|
||||
let(:commit) { project.repository.commit }
|
||||
let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
|
||||
let(:commit_status) { create(:commit_status, commit: ci_commit) }
|
||||
|
||||
describe "GET /projects/:id/repository/commits/:sha/statuses" do
|
||||
context "reporter user" do
|
||||
let(:statuses_id) { json_response.map { |status| status['id'] } }
|
||||
|
||||
before do
|
||||
@status1 = create(:commit_status, commit: ci_commit, status: 'running')
|
||||
@status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending')
|
||||
@status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running')
|
||||
@status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success')
|
||||
@status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success')
|
||||
@status6 = create(:commit_status, commit: ci_commit, status: 'success')
|
||||
end
|
||||
|
||||
it "should return latest commit statuses" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
expect(json_response).to be_an Array
|
||||
expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id)
|
||||
end
|
||||
|
||||
it "should return all commit statuses" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
expect(json_response).to be_an Array
|
||||
expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id)
|
||||
end
|
||||
|
||||
it "should return latest commit statuses for specific ref" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
expect(json_response).to be_an Array
|
||||
expect(statuses_id).to contain_exactly(@status3.id, @status5.id)
|
||||
end
|
||||
|
||||
it "should return latest commit statuses for specific name" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
expect(json_response).to be_an Array
|
||||
expect(statuses_id).to contain_exactly(@status3.id, @status4.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "guest user" do
|
||||
it "should not return project commits" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context "unauthorized user" do
|
||||
it "should not return project commits" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses")
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/repository/commits/:sha/status' do
|
||||
let(:post_url) { "/projects/#{project.id}/repository/commits/#{commit.id}/status" }
|
||||
|
||||
context 'reporter user' do
|
||||
context 'should create commit status' do
|
||||
it 'with only required parameters' do
|
||||
post api(post_url, user), state: 'success'
|
||||
expect(response.status).to eq(201)
|
||||
expect(json_response['sha']).to eq(commit.id)
|
||||
expect(json_response['status']).to eq('success')
|
||||
expect(json_response['name']).to eq('default')
|
||||
expect(json_response['ref']).to be_nil
|
||||
expect(json_response['target_url']).to be_nil
|
||||
expect(json_response['description']).to be_nil
|
||||
end
|
||||
|
||||
it 'with all optional parameters' do
|
||||
post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
|
||||
expect(response.status).to eq(201)
|
||||
expect(json_response['sha']).to eq(commit.id)
|
||||
expect(json_response['status']).to eq('success')
|
||||
expect(json_response['name']).to eq('coverage')
|
||||
expect(json_response['ref']).to eq('develop')
|
||||
expect(json_response['target_url']).to eq('url')
|
||||
expect(json_response['description']).to eq('test')
|
||||
end
|
||||
end
|
||||
|
||||
context 'should not create commit status' do
|
||||
it 'with invalid state' do
|
||||
post api(post_url, user), state: 'invalid'
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
it 'without state' do
|
||||
post api(post_url, user)
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
it 'invalid commit' do
|
||||
post api("/projects/#{project.id}/repository/commits/invalid_sha/status", user), state: 'running'
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'guest user' do
|
||||
it 'should not create commit status' do
|
||||
post api(post_url, user2)
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized user' do
|
||||
it 'should not create commit status' do
|
||||
post api(post_url)
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -47,6 +47,19 @@ describe API::API, api: true do
|
|||
get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "should return not_found for CI status" do
|
||||
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['status']).to eq('not_found')
|
||||
end
|
||||
|
||||
it "should return status for CI" do
|
||||
ci_commit = project.ensure_ci_commit(project.repository.commit.sha)
|
||||
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['status']).to eq(ci_commit.status)
|
||||
end
|
||||
end
|
||||
|
||||
context "unauthorized user" do
|
||||
|
|
|
|||
Loading…
Reference in New Issue