Commit 4032fe12 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into zj-object-store-artifacts

parents 4f45b87b 615075e1
...@@ -45,6 +45,17 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -45,6 +45,17 @@ class Projects::JobsController < Projects::ApplicationController
@builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC') @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id) @builds = @builds.where("id not in (?)", @build.id)
@pipeline = @build.pipeline @pipeline = @build.pipeline
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: BuildSerializer
.new(project: @project, current_user: @current_user)
.represent(@build, {}, BuildDetailsEntity)
end
end
end end
def trace def trace
......
...@@ -9,6 +9,8 @@ module Ci ...@@ -9,6 +9,8 @@ module Ci
belongs_to :trigger_request belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_job_id
has_many :deployments, as: :deployable has_many :deployments, as: :deployable
has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment' has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment'
...@@ -206,14 +208,19 @@ module Ci ...@@ -206,14 +208,19 @@ module Ci
end end
def merge_request def merge_request
merge_requests = MergeRequest.includes(:merge_request_diff) return @merge_request if defined?(@merge_request)
.where(source_branch: ref,
source_project: pipeline.project) @merge_request ||=
.reorder(iid: :asc) begin
merge_requests = MergeRequest.includes(:merge_request_diff)
merge_requests.find do |merge_request| .where(source_branch: ref,
merge_request.commits_sha.include?(pipeline.sha) source_project: pipeline.project)
end .reorder(iid: :desc)
merge_requests.find do |merge_request|
merge_request.commits_sha.include?(pipeline.sha)
end
end
end end
def repo_url def repo_url
...@@ -347,7 +354,7 @@ module Ci ...@@ -347,7 +354,7 @@ module Ci
end end
def has_expiring_artifacts? def has_expiring_artifacts?
artifacts_expire_at.present? artifacts_expire_at.present? && artifacts_expire_at > Time.now
end end
def keep_artifacts! def keep_artifacts!
......
...@@ -11,6 +11,13 @@ module Ci ...@@ -11,6 +11,13 @@ module Ci
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
has_one :source_pipeline, class_name: Ci::Sources::Pipeline
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_pipeline_id
has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id' has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id' has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
......
module Ci
module Sources
class Pipeline < ActiveRecord::Base
self.table_name = "ci_sources_pipelines"
belongs_to :project, class_name: Project
belongs_to :pipeline, class_name: Ci::Pipeline
belongs_to :source_project, class_name: Project, foreign_key: :source_project_id
belongs_to :source_job, class_name: Ci::Build, foreign_key: :source_job_id
belongs_to :source_pipeline, class_name: Ci::Pipeline, foreign_key: :source_pipeline_id
validates :project, presence: true
validates :pipeline, presence: true
validates :source_project, presence: true
validates :source_job, presence: true
validates :source_pipeline, presence: true
end
end
end
...@@ -127,6 +127,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -127,6 +127,11 @@ class CommitStatus < ActiveRecord::Base
false false
end end
# To be overriden when inherrited from
def retryable?
false
end
def stuck? def stuck?
false false
end end
......
...@@ -187,6 +187,10 @@ class Project < ActiveRecord::Base ...@@ -187,6 +187,10 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy has_many :deployments, dependent: :destroy
has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule' has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule'
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_project_id
has_many :source_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :project_id
has_many :path_locks, dependent: :destroy has_many :path_locks, dependent: :destroy
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
......
class BuildArtifactEntity < Grape::Entity class BuildArtifactEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :name do |build| expose :name do |job|
build.name job.name
end end
expose :path do |build| expose :artifacts_expired?, as: :expired
expose :artifacts_expire_at, as: :expire_at
expose :path do |job|
download_namespace_project_job_artifacts_path( download_namespace_project_job_artifacts_path(
build.project.namespace, project.namespace,
build.project, project,
build) job)
end
expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job|
keep_namespace_project_job_artifacts_path(
project.namespace,
project,
job)
end
expose :browse_path do |job|
browse_namespace_project_job_artifacts_path(
project.namespace,
project,
job)
end
private
alias_method :job, :object
def project
job.project
end end
end end
class BuildDetailsEntity < BuildEntity
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :user, using: UserEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
erase_namespace_project_job_path(project.namespace, project, build)
end
expose :artifacts, using: BuildArtifactEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
expose :iid do |build|
build.merge_request.iid
end
expose :path do |build|
namespace_project_merge_request_path(project.namespace, project, build.merge_request)
end
end
expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build|
new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options)
end
expose :raw_path do |build|
raw_namespace_project_build_path(project.namespace, project, build)
end
private
def build_failed_issue_options
{
title: "Build Failed ##{build.id}",
description: namespace_project_job_url(project.namespace, project, build)
}
end
def current_user
request.current_user
end
def project
build.project
end
end
...@@ -8,7 +8,7 @@ class BuildEntity < Grape::Entity ...@@ -8,7 +8,7 @@ class BuildEntity < Grape::Entity
path_to(:namespace_project_job, build) path_to(:namespace_project_job, build)
end end
expose :retry_path do |build| expose :retry_path, if: -> (*) { build&.retryable? } do |build|
path_to(:retry_namespace_project_job, build) path_to(:retry_namespace_project_job, build)
end end
......
...@@ -48,7 +48,7 @@ class MergeRequestEntity < IssuableEntity ...@@ -48,7 +48,7 @@ class MergeRequestEntity < IssuableEntity
expose :merge_commit_sha expose :merge_commit_sha
expose :merge_commit_message expose :merge_commit_message
expose :head_pipeline, with: PipelineEntity, as: :pipeline expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
# Booleans # Booleans
expose :work_in_progress?, as: :work_in_progress expose :work_in_progress?, as: :work_in_progress
......
class PipelineDetailsEntity < PipelineEntity
expose :details do
expose :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity
end
expose :triggered_by_pipeline, as: :triggered_by, with: TriggeredPipelineEntity
expose :triggered_pipelines, as: :triggered, using: TriggeredPipelineEntity
end
...@@ -7,6 +7,8 @@ class PipelineEntity < Grape::Entity ...@@ -7,6 +7,8 @@ class PipelineEntity < Grape::Entity
expose :coverage expose :coverage
expose :source expose :source
expose :created_at, :updated_at
expose :path do |pipeline| expose :path do |pipeline|
namespace_project_pipeline_path( namespace_project_pipeline_path(
pipeline.project.namespace, pipeline.project.namespace,
...@@ -14,15 +16,6 @@ class PipelineEntity < Grape::Entity ...@@ -14,15 +16,6 @@ class PipelineEntity < Grape::Entity
pipeline) pipeline)
end end
expose :details do
expose :detailed_status, as: :status, with: StatusEntity
expose :duration
expose :finished_at
expose :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity
end
expose :flags do expose :flags do
expose :latest?, as: :latest expose :latest?, as: :latest
expose :stuck?, as: :stuck expose :stuck?, as: :stuck
...@@ -31,6 +24,12 @@ class PipelineEntity < Grape::Entity ...@@ -31,6 +24,12 @@ class PipelineEntity < Grape::Entity
expose :can_cancel?, as: :cancelable expose :can_cancel?, as: :cancelable
end end
expose :details do
expose :detailed_status, as: :status, with: StatusEntity
expose :duration
expose :finished_at
end
expose :ref do expose :ref do
expose :name do |pipeline| expose :name do |pipeline|
pipeline.ref pipeline.ref
...@@ -47,7 +46,6 @@ class PipelineEntity < Grape::Entity ...@@ -47,7 +46,6 @@ class PipelineEntity < Grape::Entity
end end
expose :commit, using: CommitEntity expose :commit, using: CommitEntity
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
expose :retry_path, if: -> (*) { can_retry? } do |pipeline| expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_namespace_project_pipeline_path(pipeline.project.namespace, retry_namespace_project_pipeline_path(pipeline.project.namespace,
...@@ -61,7 +59,7 @@ class PipelineEntity < Grape::Entity ...@@ -61,7 +59,7 @@ class PipelineEntity < Grape::Entity
pipeline.id) pipeline.id)
end end
expose :created_at, :updated_at expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
private private
......
class PipelineSerializer < BaseSerializer class PipelineSerializer < BaseSerializer
InvalidResourceError = Class.new(StandardError) InvalidResourceError = Class.new(StandardError)
entity PipelineEntity entity PipelineDetailsEntity
def with_pagination(request, response) def with_pagination(request, response)
tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
...@@ -18,6 +18,8 @@ class PipelineSerializer < BaseSerializer ...@@ -18,6 +18,8 @@ class PipelineSerializer < BaseSerializer
:cancelable_statuses, :cancelable_statuses,
:trigger_requests, :trigger_requests,
:project, :project,
{ triggered_by_pipeline: [:project, :user] },
{ triggered_pipelines: [:project, :user] },
{ pending_builds: :project }, { pending_builds: :project },
{ manual_actions: :project }, { manual_actions: :project },
{ artifacts: :project } { artifacts: :project }
......
class RunnerEntity < Grape::Entity
include RequestAwareEntity
expose :id, :description
expose :edit_path,
if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner|
edit_namespace_project_runner_path(project.namespace, project, runner)
end
private
alias_method :runner, :object
def project
request.project
end
end
class TriggeredPipelineEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :user, using: UserEntity
expose :active?, as: :active
expose :coverage
expose :source
expose :path do |pipeline|
namespace_project_pipeline_path(
pipeline.project.namespace,
pipeline.project,
pipeline)
end
expose :details do
expose :detailed_status, as: :status, with: StatusEntity
end
expose :project, using: ProjectEntity
private
alias_method :pipeline, :object
def detailed_status
pipeline.detailed_status(request.current_user)
end
end
...@@ -10,13 +10,19 @@ class ExpirePipelineCacheWorker ...@@ -10,13 +10,19 @@ class ExpirePipelineCacheWorker
store = Gitlab::EtagCaching::Store.new store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path(project)) store.touch(project_pipelines_path(project))
store.touch(project_pipeline_path(project, pipeline)) store.touch(project_pipeline_path(pipeline))
store.touch(commit_pipelines_path(project, pipeline.commit)) if pipeline.commit store.touch(commit_pipelines_path(project, pipeline.commit)) if pipeline.commit
store.touch(new_merge_request_pipelines_path(project)) store.touch(new_merge_request_pipelines_path(project))
each_pipelines_merge_request_path(project, pipeline) do |path| each_pipelines_merge_request_path(project, pipeline) do |path|
store.touch(path) store.touch(path)
end end
store.touch(project_pipeline_path(pipeline.triggered_by_pipeline)) if pipeline.triggered_by_pipeline
pipeline.triggered_pipelines.each do |triggered|
store.touch(project_pipeline_path(triggered))
end
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline) Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
end end
...@@ -29,10 +35,10 @@ class ExpirePipelineCacheWorker ...@@ -29,10 +35,10 @@ class ExpirePipelineCacheWorker
format: :json) format: :json)
end end
def project_pipeline_path(project, pipeline) def project_pipeline_path(pipeline)
Gitlab::Routing.url_helpers.namespace_project_pipeline_path( Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
project.namespace, pipeline.project.namespace,
project, pipeline.project,
pipeline, pipeline,
format: :json) format: :json)
end end
......
---
title: Add relation between Pipelines
merge_request:
author:
---
title: Job details page update real time
merge_request: 11651
author:
...@@ -75,7 +75,15 @@ class Gitlab::Seeder::Pipelines ...@@ -75,7 +75,15 @@ class Gitlab::Seeder::Pipelines
def create_master_pipelines def create_master_pipelines
@project.repository.commits('master', limit: 4).map do |commit| @project.repository.commits('master', limit: 4).map do |commit|
create_pipeline!(@project, 'master', commit) create_pipeline!(@project, 'master', commit).tap do |pipeline|
random_pipeline.tap do |triggered_by_pipeline|
triggered_by_pipeline.sourced_pipelines.create(
source_job: triggered_by_pipeline.builds.all.sample,
source_project: triggered_by_pipeline.project,
project: pipeline.project,
pipeline: pipeline)
end
end
end end
rescue rescue
[] []
...@@ -96,7 +104,6 @@ class Gitlab::Seeder::Pipelines ...@@ -96,7 +104,6 @@ class Gitlab::Seeder::Pipelines
[] []
end end
def create_pipeline!(project, ref, commit) def create_pipeline!(project, ref, commit)
project.pipelines.create(sha: commit.id, ref: ref, source: :push) project.pipelines.create(sha: commit.id, ref: ref, source: :push)
end end
...@@ -151,6 +158,10 @@ class Gitlab::Seeder::Pipelines ...@@ -151,6 +158,10 @@ class Gitlab::Seeder::Pipelines
@project.team.users.sample @project.team.users.sample
end end
def random_pipeline
Ci::Pipeline.limit(4).all.sample
end
def build_status def build_status
Ci::Build::AVAILABLE_STATUSES.sample Ci::Build::AVAILABLE_STATUSES.sample
end end
......
class CreatePipelineSourcePipeline < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :ci_sources_pipelines, force: :cascade do |t|
t.integer :project_id
t.integer :pipeline_id
t.integer :source_project_id
t.integer :source_job_id
t.integer :source_pipeline_id
end
end
end
class AddCiPipelineSourcePipelineIndexes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_sources_pipelines, :project_id
add_concurrent_index :ci_sources_pipelines, :pipeline_id
add_concurrent_index :ci_sources_pipelines, :source_project_id
add_concurrent_index :ci_sources_pipelines, :source_job_id
add_concurrent_index :ci_sources_pipelines, :source_pipeline_id
end
def down
remove_concurrent_index :ci_sources_pipelines, :project_id if index_exists? :ci_sources_pipelines, :project_id
remove_concurrent_index :ci_sources_pipelines, :pipeline_id if index_exists? :ci_sources_pipelines, :pipeline_id
remove_concurrent_index :ci_sources_pipelines, :source_project_id if index_exists? :ci_sources_pipelines, :source_project_id
remove_concurrent_index :ci_sources_pipelines, :source_job_id if index_exists? :ci_sources_pipelines, :source_job_id
remove_concurrent_index :ci_sources_pipelines, :source_pipeline_id if index_exists? :ci_sources_pipelines, :source_pipeline_id
end
end
class AddCiPipelineSourcePipelineForeignKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :ci_sources_pipelines, :projects, column: :project_id
add_concurrent_foreign_key :ci_sources_pipelines, :ci_pipelines, column: :pipeline_id
add_concurrent_foreign_key :ci_sources_pipelines, :projects, column: :source_project_id
add_concurrent_foreign_key :ci_sources_pipelines, :ci_builds, column: :source_job_id
add_concurrent_foreign_key :ci_sources_pipelines, :ci_pipelines, column: :source_pipeline_id
end
def down
remove_foreign_key :ci_sources_pipelines, column: :project_id
remove_foreign_key :ci_sources_pipelines, column: :pipeline_id
remove_foreign_key :ci_sources_pipelines, column: :source_project_id
remove_foreign_key :ci_sources_pipelines, column: :source_job_id
remove_foreign_key :ci_sources_pipelines, column: :source_pipeline_id
end
end
...@@ -379,6 +379,20 @@ ActiveRecord::Schema.define(version: 20170602003304) do ...@@ -379,6 +379,20 @@ ActiveRecord::Schema.define(version: 20170602003304) do
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
create_table "ci_sources_pipelines", force: :cascade do |t|
t.integer "project_id"
t.integer "pipeline_id"
t.integer "source_project_id"
t.integer "source_job_id"
t.integer "source_pipeline_id"
end
add_index "ci_sources_pipelines", ["pipeline_id"], name: "index_ci_pipeline_source_pipelines_on_pipeline_id", using: :btree
add_index "ci_sources_pipelines", ["project_id"], name: "index_ci_pipeline_source_pipelines_on_project_id", using: :btree
add_index "ci_sources_pipelines", ["source_job_id"], name: "index_ci_pipeline_source_pipelines_on_source_job_id", using: :btree
add_index "ci_sources_pipelines", ["source_pipeline_id"], name: "index_ci_pipeline_source_pipelines_on_source_pipeline_id", using: :btree
add_index "ci_sources_pipelines", ["source_project_id"], name: "index_ci_pipeline_source_pipelines_on_source_project_id", using: :btree
create_table "ci_trigger_requests", force: :cascade do |t| create_table "ci_trigger_requests", force: :cascade do |t|
t.integer "trigger_id", null: false t.integer "trigger_id", null: false
t.text "variables" t.text "variables"
...@@ -1685,6 +1699,11 @@ ActiveRecord::Schema.define(version: 20170602003304) do ...@@ -1685,6 +1699,11 @@ ActiveRecord::Schema.define(version: 20170602003304) do
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
add_foreign_key "ci_sources_pipelines", "ci_builds", column: "source_job_id", name: "fk_3f0c88d7dc", on_delete: :cascade
add_foreign_key "ci_sources_pipelines", "ci_pipelines", column: "pipeline_id", name: "fk_b8c0fac459", on_delete: :cascade
add_foreign_key "ci_sources_pipelines", "ci_pipelines", column: "source_pipeline_id", name: "fk_3a3e3cb83a", on_delete: :cascade
add_foreign_key "ci_sources_pipelines", "projects", column: "source_project_id", name: "fk_8868d0f3e4", on_delete: :cascade
add_foreign_key "ci_sources_pipelines", "projects", name: "fk_83b4346e48", on_delete: :cascade
add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
......
...@@ -9,8 +9,8 @@ module Gitlab ...@@ -9,8 +9,8 @@ module Gitlab
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route # - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route # - Ending in `issues/id`/realtime_changes` for the `issue_title` route
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new commit pipelines merge_requests builds
environments].freeze new environments].freeze
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape))) RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape)))
...@@ -43,6 +43,10 @@ module Gitlab ...@@ -43,6 +43,10 @@ module Gitlab
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z), %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
'project_pipeline' 'project_pipeline'
), ),
Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/builds/\d+\.json\z),
'project_build'
),
Gitlab::EtagCaching::Router::Route.new( Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z), %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z),
'environments' 'environments'
......
...@@ -101,26 +101,49 @@ describe Projects::JobsController do ...@@ -101,26 +101,49 @@ describe Projects::JobsController do
end end
describe 'GET show' do describe 'GET show' do
context 'when build exists' do let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
before do context 'when requesting HTML' do
get_show(id: build.id) context 'when build exists' do
before do
get_show(id: build.id)
end
it 'has a build' do
expect(response).to have_http_status(:ok)
expect(assigns(:build).id).to eq(build.id)
end
end end
it 'has a build' do context 'when build does not exist' do
expect(response).to have_http_status(:ok) before do
expect(assigns(:build).id).to eq(build.id) get_show(id: 1234)
end
it 'renders not_found' do
expect(response).to have_http_status(:not_found)
end
end end
end end
context 'when build does not exist' do context 'when requesting JSON' do
let(:merge_request) { create(:merge_request, source_project: project) }
before do before do
get_show(id: 1234) project.add_developer(user)
sign_in(user)
allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
get_show(id: build.id, format: :json)
end end
it 'renders not_found' do it 'exposes needed information' do
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:ok)
expect(json_response['raw_path']).to match(/builds\/\d+\/raw\z/)
expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/)
expect(json_response['new_issue_path'])
.to include('/issues/new')
end end
end end
......
FactoryGirl.define do
factory :ci_sources_pipeline, class: Ci::Sources::Pipeline do
after(:build) do |source|
source.project ||= source.pipeline.project
source.source_pipeline ||= source.source_job.pipeline
source.source_project ||= source.source_pipeline.project
end
source_job factory: :ci_build
pipeline factory: :ci_empty_pipeline
end
end
...@@ -67,6 +67,17 @@ describe Gitlab::EtagCaching::Router do ...@@ -67,6 +67,17 @@ describe Gitlab::EtagCaching::Router do
expect(result.name).to eq 'merge_request_pipelines' expect(result.name).to eq 'merge_request_pipelines'
end end
it 'matches build endpoint' do
env = build_env(
'/my-group/my-project/builds/234.json'
)
result = described_class.match(env)
expect(result).to be_present
expect(result.name).to eq 'project_build'
end
it 'does not match blob with confusing name' do it 'does not match blob with confusing name' do
env = build_env( env = build_env(
'/my-group/my-project/blob/master/pipelines.json' '/my-group/my-project/blob/master/pipelines.json'
......
...@@ -108,6 +108,10 @@ pipelines: ...@@ -108,6 +108,10 @@ pipelines:
- artifacts - artifacts
- pipeline_schedule - pipeline_schedule
- merge_requests - merge_requests
- source_pipeline
- sourced_pipelines
- triggered_by_pipeline
- triggered_pipelines
statuses: statuses:
- project - project
- pipeline - pipeline
...@@ -263,6 +267,8 @@ project: ...@@ -263,6 +267,8 @@ project:
- container_repositories - container_repositories
- uploads - uploads
- mirror_data - mirror_data
- source_pipelines
- sourced_pipelines
award_emoji: award_emoji:
- awardable - awardable
- user - user
......
...@@ -17,6 +17,7 @@ describe Ci::Build, :models do ...@@ -17,6 +17,7 @@ describe Ci::Build, :models do
it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) } it { is_expected.to belong_to(:erased_by) }
it { is_expected.to have_many(:deployments) } it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to validate_presence_of(:ref) } it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) } it { is_expected.to respond_to(:trace) }
......
...@@ -20,6 +20,10 @@ describe Ci::Pipeline, models: true do ...@@ -20,6 +20,10 @@ describe Ci::Pipeline, models: true do
it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:auto_canceled_pipelines) } it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) } it { is_expected.to have_many(:auto_canceled_jobs) }
it { is_expected.to have_one(:source_pipeline) }
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_one(:triggered_by_pipeline) }
it { is_expected.to have_many(:triggered_pipelines) }
it { is_expected.to validate_presence_of(:sha) } it { is_expected.to validate_presence_of(:sha) }
it { is_expected.to validate_presence_of(:status) } it { is_expected.to validate_presence_of(:status) }
......
require 'spec_helper'
describe Ci::Sources::Pipeline, models: true do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to belong_to(:source_project) }
it { is_expected.to belong_to(:source_job) }
it { is_expected.to belong_to(:source_pipeline) }
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:pipeline) }
it { is_expected.to validate_presence_of(:source_project) }
it { is_expected.to validate_presence_of(:source_job) }
it { is_expected.to validate_presence_of(:source_pipeline) }
end
...@@ -77,6 +77,8 @@ describe Project, models: true do ...@@ -77,6 +77,8 @@ describe Project, models: true do
it { is_expected.to have_many(:approver_groups).dependent(:destroy) } it { is_expected.to have_many(:approver_groups).dependent(:destroy) }
it { is_expected.to have_many(:uploads).dependent(:destroy) } it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) } it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) }
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_many(:source_pipelines) }
context 'after initialized' do context 'after initialized' do
it "has a project_feature" do it "has a project_feature" do
......
require 'spec_helper' require 'spec_helper'
describe BuildArtifactEntity do describe BuildArtifactEntity do
let(:job) { create(:ci_build, name: 'test:job') } let(:job) { create(:ci_build, name: 'test:job', artifacts_expire_at: 1.hour.from_now) }
let(:entity) do let(:entity) do
described_class.new(job, request: double) described_class.new(job, request: double)
...@@ -14,9 +14,19 @@ describe BuildArtifactEntity do ...@@ -14,9 +14,19 @@ describe BuildArtifactEntity do
expect(subject[:name]).to eq 'test:job' expect(subject[:name]).to eq 'test:job'
end end
it 'contains path to the artifacts' do it 'exposes information about expiration of artifacts' do
expect(subject).to include(:expired, :expire_at)
end
it 'contains paths to the artifacts' do
expect(subject[:path]) expect(subject[:path])
.to include "jobs/#{job.id}/artifacts/download" .to include "jobs/#{job.id}/artifacts/download"
expect(subject[:keep_path])
.to include "jobs/#{job.id}/artifacts/keep"
expect(subject[:browse_path])
.to include "jobs/#{job.id}/artifacts/browse"
end end
end end
end end
require 'spec_helper'
describe BuildDetailsEntity do
set(:user) { create(:admin) }
it 'inherits from BuildEntity' do
expect(described_class).to be < BuildEntity
end
describe '#as_json' do
let(:project) { create(:project, :repository) }
let!(:build) { create(:ci_build, :failed, project: project) }
let(:request) { double('request') }
let(:entity) { described_class.new(build, request: request, current_user: user, project: project) }
subject { entity.as_json }
before do
allow(request).to receive(:current_user).and_return(user)
end
context 'when the user has access to issues and merge requests' do
let!(:merge_request) do
create(:merge_request, source_project: project, source_branch: build.ref)
end
before do
allow(build).to receive(:merge_request).and_return(merge_request)
end
it 'contains the needed key value pairs' do
expect(subject).to include(:coverage, :erased_at, :duration)
expect(subject).to include(:artifacts, :runner, :pipeline)
expect(subject).to include(:raw_path, :merge_request)
expect(subject).to include(:new_issue_path)
end
it 'exposes details of the merge request' do
expect(subject[:merge_request]).to include(:iid, :path)
end
context 'when the build has been erased' do
let!(:build) { create(:ci_build, :erasable, project: project) }
it 'exposes the user whom erased the build' do
expect(subject).to include(:erase_path)
end
end
context 'when the build has been erased' do
let!(:build) { create(:ci_build, erased_at: Time.now, project: project, erased_by: user) }
it 'exposes the user whom erased the build' do
expect(subject).to include(:erased_by)
end
end
end
context 'when the user can only read the build' do
let(:user) { create(:user) }
it "won't display the paths to issues and merge requests" do
expect(subject['new_issue_path']).to be_nil
expect(subject['merge_request_path']).to be_nil
end
end
end
end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe BuildEntity do describe BuildEntity do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build, :failed) }
let(:project) { build.project } let(:project) { build.project }
let(:request) { double('request') } let(:request) { double('request') }
...@@ -18,6 +18,7 @@ describe BuildEntity do ...@@ -18,6 +18,7 @@ describe BuildEntity do
it 'contains paths to build page and retry action' do it 'contains paths to build page and retry action' do
expect(subject).to include(:build_path, :retry_path) expect(subject).to include(:build_path, :retry_path)
expect(subject[:retry_path]).not_to be_nil
end end
it 'does not contain sensitive information' do it 'does not contain sensitive information' do
......
...@@ -26,7 +26,7 @@ describe MergeRequestEntity do ...@@ -26,7 +26,7 @@ describe MergeRequestEntity do
pipeline = build_stubbed(:ci_pipeline) pipeline = build_stubbed(:ci_pipeline)
allow(resource).to receive(:head_pipeline).and_return(pipeline) allow(resource).to receive(:head_pipeline).and_return(pipeline)
pipeline_payload = PipelineEntity pipeline_payload = PipelineDetailsEntity
.represent(pipeline, request: req) .represent(pipeline, request: req)
.as_json .as_json
......
require 'spec_helper'
describe PipelineDetailsEntity do
set(:user) { create(:user) }
let(:request) { double('request') }
it 'inherrits from PipelineEntity' do
expect(described_class).to be < PipelineEntity
end
before do
allow(request).to receive(:current_user).and_return(user)
end
let(:entity) do
described_class.represent(pipeline, request: request)
end
describe '#as_json' do
subject { entity.as_json }
context 'when pipeline is empty' do
let(:pipeline) { create(:ci_empty_pipeline) }
it 'contains details' do
expect(subject).to include :details
expect(subject[:details])
.to include :duration, :finished_at
expect(subject[:details])
.to include :stages, :artifacts, :manual_actions
expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
end
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
.to include :latest, :stuck,
:yaml_errors, :retryable, :cancelable
end
end
context 'when pipeline is retryable' do
let(:project) { create(:empty_project) }
let(:pipeline) do
create(:ci_pipeline, status: :success, project: project)
end
before do
create(:ci_build, :failed, pipeline: pipeline)
end
context 'user has ability to retry pipeline' do
before { project.team << [user, :developer] }
it 'retryable flag is true' do
expect(subject[:flags][:retryable]).to eq true
end
end
context 'user does not have ability to retry pipeline' do
it 'retryable flag is false' do
expect(subject[:flags][:retryable]).to eq false
end
end
end
context 'when pipeline is cancelable' do
let(:project) { create(:empty_project) }
let(:pipeline) do
create(:ci_pipeline, status: :running, project: project)
end
before do
create(:ci_build, :pending, pipeline: pipeline)
end
context 'user has ability to cancel pipeline' do
before { project.add_developer(user) }
it 'cancelable flag is true' do
expect(subject[:flags][:cancelable]).to eq true
end
end
context 'user does not have ability to cancel pipeline' do
it 'cancelable flag is false' do
expect(subject[:flags][:cancelable]).to eq false
end
end
end
context 'when pipeline has YAML errors' do
let(:pipeline) do
create(:ci_pipeline, config: { rspec: { invalid: :value } })
end
it 'contains information about error' do
expect(subject[:yaml_errors]).to be_present
end
it 'contains flag that indicates there are errors' do
expect(subject[:flags][:yaml_errors]).to be true
end
end
context 'when pipeline does not have YAML errors' do
let(:pipeline) { create(:ci_empty_pipeline) }
it 'does not contain field that normally holds an error' do
expect(subject).not_to have_key(:yaml_errors)
end
it 'contains flag that indicates there are no errors' do
expect(subject[:flags][:yaml_errors]).to be false
end
end
context 'when pipeline is triggered by other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
before do
create(:ci_sources_pipeline, pipeline: pipeline)
end
it 'contains an information about depedent pipeline' do
expect(subject[:triggered_by]).to be_a(Hash)
expect(subject[:triggered_by][:path]).not_to be_nil
expect(subject[:triggered_by][:details]).not_to be_nil
expect(subject[:triggered_by][:details][:status]).not_to be_nil
expect(subject[:triggered_by][:project]).not_to be_nil
end
end
context 'when pipeline triggered other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:build) { create(:ci_build, pipeline: pipeline) }
before do
create(:ci_sources_pipeline, source_job: build)
create(:ci_sources_pipeline, source_job: build)
end
it 'contains an information about depedent pipeline' do
expect(subject[:triggered]).to be_a(Array)
expect(subject[:triggered].length).to eq(2)
expect(subject[:triggered].first[:path]).not_to be_nil
expect(subject[:triggered].first[:details]).not_to be_nil
expect(subject[:triggered].first[:details][:status]).not_to be_nil
expect(subject[:triggered].first[:project]).not_to be_nil
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe PipelineEntity do describe PipelineEntity do
let(:user) { create(:user) } set(:user) { create(:user) }
let(:request) { double('request') } let(:request) { double('request') }
before do before do
...@@ -28,8 +28,6 @@ describe PipelineEntity do ...@@ -28,8 +28,6 @@ describe PipelineEntity do
expect(subject).to include :details expect(subject).to include :details
expect(subject[:details]) expect(subject[:details])
.to include :duration, :finished_at .to include :duration, :finished_at
expect(subject[:details])
.to include :stages, :artifacts, :manual_actions
expect(subject[:details][:status]).to include :icon, :favicon, :text, :label expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
end end
...@@ -55,20 +53,12 @@ describe PipelineEntity do ...@@ -55,20 +53,12 @@ describe PipelineEntity do
context 'user has ability to retry pipeline' do context 'user has ability to retry pipeline' do
before { project.team << [user, :developer] } before { project.team << [user, :developer] }
it 'retryable flag is true' do
expect(subject[:flags][:retryable]).to eq true
end
it 'contains retry path' do it 'contains retry path' do
expect(subject[:retry_path]).to be_present expect(subject[:retry_path]).to be_present
end end
end end
context 'user does not have ability to retry pipeline' do context 'user does not have ability to retry pipeline' do
it 'retryable flag is false' do
expect(subject[:flags][:retryable]).to eq false
end
it 'does not contain retry path' do it 'does not contain retry path' do
expect(subject).not_to have_key(:retry_path) expect(subject).not_to have_key(:retry_path)
end end
...@@ -87,11 +77,7 @@ describe PipelineEntity do ...@@ -87,11 +77,7 @@ describe PipelineEntity do
end end
context 'user has ability to cancel pipeline' do context 'user has ability to cancel pipeline' do
before { project.team << [user, :developer] } before { project.add_developer(user) }
it 'cancelable flag is true' do
expect(subject[:flags][:cancelable]).to eq true
end
it 'contains cancel path' do it 'contains cancel path' do
expect(subject[:cancel_path]).to be_present expect(subject[:cancel_path]).to be_present
...@@ -99,42 +85,12 @@ describe PipelineEntity do ...@@ -99,42 +85,12 @@ describe PipelineEntity do
end end
context 'user does not have ability to cancel pipeline' do context 'user does not have ability to cancel pipeline' do
it 'cancelable flag is false' do
expect(subject[:flags][:cancelable]).to eq false
end
it 'does not contain cancel path' do it 'does not contain cancel path' do
expect(subject).not_to have_key(:cancel_path) expect(subject).not_to have_key(:cancel_path)
end end
end end
end end
context 'when pipeline has YAML errors' do
let(:pipeline) do
create(:ci_pipeline, config: { rspec: { invalid: :value } })
end
it 'contains flag that indicates there are errors' do
expect(subject[:flags][:yaml_errors]).to be true
end
it 'contains information about error' do
expect(subject[:yaml_errors]).to be_present
end
end
context 'when pipeline does not have YAML errors' do
let(:pipeline) { create(:ci_empty_pipeline) }
it 'contains flag that indicates there are no errors' do
expect(subject[:flags][:yaml_errors]).to be false
end
it 'does not contain field that normally holds an error' do
expect(subject).not_to have_key(:yaml_errors)
end
end
context 'when pipeline ref is empty' do context 'when pipeline ref is empty' do
let(:pipeline) { create(:ci_empty_pipeline) } let(:pipeline) { create(:ci_empty_pipeline) }
......
...@@ -113,7 +113,7 @@ describe PipelineSerializer do ...@@ -113,7 +113,7 @@ describe PipelineSerializer do
it "verifies number of queries" do it "verifies number of queries" do
recorded = ActiveRecord::QueryRecorder.new { subject } recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.count).to be_within(1).of(61) expect(recorded.count).to be_within(1).of(64)
expect(recorded.cached_count).to eq(0) expect(recorded.cached_count).to eq(0)
end end
......
require 'spec_helper'
describe RunnerEntity do
let(:runner) { create(:ci_runner, :specific) }
let(:entity) { described_class.new(runner, request: request, current_user: user) }
let(:request) { double('request') }
let(:project) { create(:empty_project) }
let(:user) { create(:admin) }
before do
allow(request).to receive(:current_user).and_return(user)
allow(request).to receive(:project).and_return(project)
end
describe '#as_json' do
subject { entity.as_json }
it 'contains required fields' do
expect(subject).to include(:id, :description)
expect(subject).to include(:edit_path)
end
end
end
...@@ -22,7 +22,7 @@ describe Ci::RetryBuildService, :services do ...@@ -22,7 +22,7 @@ describe Ci::RetryBuildService, :services do
%i[type lock_version target_url base_tags %i[type lock_version target_url base_tags
commit_id deployments erased_by_id last_deployment project_id commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried user_id auto_canceled_by_id retried sourced_pipelines
artifacts_file_store artifacts_metadata_store].freeze artifacts_file_store artifacts_metadata_store].freeze
shared_examples 'build duplication' do shared_examples 'build duplication' do
......
...@@ -42,5 +42,34 @@ describe ExpirePipelineCacheWorker do ...@@ -42,5 +42,34 @@ describe ExpirePipelineCacheWorker do
subject.perform(pipeline.id) subject.perform(pipeline.id)
end end
context 'when pipeline is triggered by other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:source) { create(:ci_sources_pipeline, pipeline: pipeline) }
it 'updates the cache of dependent pipeline' do
dependent_pipeline_path = "/#{source.source_project.full_path}/pipelines/#{source.source_pipeline.id}.json"
allow_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch)
expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(dependent_pipeline_path)
subject.perform(pipeline.id)
end
end
context 'when pipeline triggered other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:source) { create(:ci_sources_pipeline, source_job: build) }
it 'updates the cache of dependent pipeline' do
dependent_pipeline_path = "/#{source.project.full_path}/pipelines/#{source.pipeline.id}.json"
allow_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch)
expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(dependent_pipeline_path)
subject.perform(pipeline.id)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment