Commit 615075e1 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'cross-project-triggers-ee' into 'master'

Cross project triggers

See merge request !2042
parents 36eaa3de 23712d20
......@@ -45,6 +45,17 @@ class Projects::JobsController < Projects::ApplicationController
@builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id)
@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
def trace
......
......@@ -9,6 +9,8 @@ module Ci
belongs_to :trigger_request
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_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment'
......@@ -206,14 +208,19 @@ module Ci
end
def merge_request
merge_requests = MergeRequest.includes(:merge_request_diff)
.where(source_branch: ref,
source_project: pipeline.project)
.reorder(iid: :asc)
merge_requests.find do |merge_request|
merge_request.commits_sha.include?(pipeline.sha)
end
return @merge_request if defined?(@merge_request)
@merge_request ||=
begin
merge_requests = MergeRequest.includes(:merge_request_diff)
.where(source_branch: ref,
source_project: pipeline.project)
.reorder(iid: :desc)
merge_requests.find do |merge_request|
merge_request.commits_sha.include?(pipeline.sha)
end
end
end
def repo_url
......@@ -337,7 +344,7 @@ module Ci
end
def has_expiring_artifacts?
artifacts_expire_at.present?
artifacts_expire_at.present? && artifacts_expire_at > Time.now
end
def keep_artifacts!
......
......@@ -11,6 +11,13 @@ module Ci
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
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_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
false
end
# To be overriden when inherrited from
def retryable?
false
end
def stuck?
false
end
......
......@@ -187,6 +187,10 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy
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 :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
......
class BuildArtifactEntity < Grape::Entity
include RequestAwareEntity
expose :name do |build|
build.name
expose :name do |job|
job.name
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(
build.project.namespace,
build.project,
build)
project.namespace,
project,
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
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
path_to(:namespace_project_job, build)
end
expose :retry_path do |build|
expose :retry_path, if: -> (*) { build&.retryable? } do |build|
path_to(:retry_namespace_project_job, build)
end
......
......@@ -48,7 +48,7 @@ class MergeRequestEntity < IssuableEntity
expose :merge_commit_sha
expose :merge_commit_message
expose :head_pipeline, with: PipelineEntity, as: :pipeline
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
# Booleans
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
expose :coverage
expose :source
expose :created_at, :updated_at
expose :path do |pipeline|
namespace_project_pipeline_path(
pipeline.project.namespace,
......@@ -14,15 +16,6 @@ class PipelineEntity < Grape::Entity
pipeline)
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 :latest?, as: :latest
expose :stuck?, as: :stuck
......@@ -31,6 +24,12 @@ class PipelineEntity < Grape::Entity
expose :can_cancel?, as: :cancelable
end
expose :details do
expose :detailed_status, as: :status, with: StatusEntity
expose :duration
expose :finished_at
end
expose :ref do
expose :name do |pipeline|
pipeline.ref
......@@ -47,7 +46,6 @@ class PipelineEntity < Grape::Entity
end
expose :commit, using: CommitEntity
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_namespace_project_pipeline_path(pipeline.project.namespace,
......@@ -61,7 +59,7 @@ class PipelineEntity < Grape::Entity
pipeline.id)
end
expose :created_at, :updated_at
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
private
......
class PipelineSerializer < BaseSerializer
InvalidResourceError = Class.new(StandardError)
entity PipelineEntity
entity PipelineDetailsEntity
def with_pagination(request, response)
tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
......@@ -18,6 +18,8 @@ class PipelineSerializer < BaseSerializer
:cancelable_statuses,
:trigger_requests,
:project,
{ triggered_by_pipeline: [:project, :user] },
{ triggered_pipelines: [:project, :user] },
{ pending_builds: :project },
{ manual_actions: :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
store = Gitlab::EtagCaching::Store.new
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(new_merge_request_pipelines_path(project))
each_pipelines_merge_request_path(project, pipeline) do |path|
store.touch(path)
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)
end
......@@ -29,10 +35,10 @@ class ExpirePipelineCacheWorker
format: :json)
end
def project_pipeline_path(project, pipeline)
def project_pipeline_path(pipeline)
Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
project.namespace,
project,
pipeline.project.namespace,
pipeline.project,
pipeline,
format: :json)
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
def create_master_pipelines
@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
rescue
[]
......@@ -96,7 +104,6 @@ class Gitlab::Seeder::Pipelines
[]
end
def create_pipeline!(project, ref, commit)
project.pipelines.create(sha: commit.id, ref: ref, source: :push)
end
......@@ -151,6 +158,10 @@ class Gitlab::Seeder::Pipelines
@project.team.users.sample
end
def random_pipeline
Ci::Pipeline.limit(4).all.sample
end
def build_status
Ci::Build::AVAILABLE_STATUSES.sample
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
......@@ -377,6 +377,20 @@ ActiveRecord::Schema.define(version: 20170602003304) do
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
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|
t.integer "trigger_id", null: false
t.text "variables"
......@@ -1683,6 +1697,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_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_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_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
......
......@@ -9,8 +9,8 @@ module Gitlab
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new
environments].freeze
commit pipelines merge_requests builds
new environments].freeze
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape)))
......@@ -43,6 +43,10 @@ module Gitlab
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
'project_pipeline'
),
Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/builds/\d+\.json\z),
'project_build'
),
Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z),
'environments'
......
......@@ -101,26 +101,49 @@ describe Projects::JobsController do
end
describe 'GET show' do
context 'when build exists' do
let!(:build) { create(:ci_build, pipeline: pipeline) }
let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
before do
get_show(id: build.id)
context 'when requesting HTML' do
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
it 'has a build' do
expect(response).to have_http_status(:ok)
expect(assigns(:build).id).to eq(build.id)
context 'when build does not exist' do
before do
get_show(id: 1234)
end
it 'renders not_found' do
expect(response).to have_http_status(:not_found)
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
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
it 'renders not_found' do
expect(response).to have_http_status(:not_found)
it 'exposes needed information' do
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
......
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
expect(result.name).to eq 'merge_request_pipelines'
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
env = build_env(
'/my-group/my-project/blob/master/pipelines.json'
......
......@@ -108,6 +108,10 @@ pipelines:
- artifacts
- pipeline_schedule
- merge_requests
- source_pipeline
- sourced_pipelines
- triggered_by_pipeline
- triggered_pipelines
statuses:
- project
- pipeline
......@@ -263,6 +267,8 @@ project:
- container_repositories
- uploads
- mirror_data
- source_pipelines
- sourced_pipelines
award_emoji:
- awardable
- user
......
......@@ -17,6 +17,7 @@ describe Ci::Build, :models do
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
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 respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
......
......@@ -20,6 +20,10 @@ describe Ci::Pipeline, models: true do
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:auto_canceled_pipelines) }
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(: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
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(:pipeline_schedules).dependent(:destroy) }
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_many(:source_pipelines) }
context 'after initialized' do
it "has a project_feature" do
......
require 'spec_helper'
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
described_class.new(job, request: double)
......@@ -14,9 +14,19 @@ describe BuildArtifactEntity do
expect(subject[:name]).to eq 'test:job'
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])
.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
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'
describe BuildEntity do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:build) { create(:ci_build, :failed) }
let(:project) { build.project }
let(:request) { double('request') }
......@@ -18,6 +18,7 @@ describe BuildEntity do
it 'contains paths to build page and retry action' do
expect(subject).to include(:build_path, :retry_path)
expect(subject[:retry_path]).not_to be_nil
end
it 'does not contain sensitive information' do
......
......@@ -26,7 +26,7 @@ describe MergeRequestEntity do
pipeline = build_stubbed(:ci_pipeline)
allow(resource).to receive(:head_pipeline).and_return(pipeline)
pipeline_payload = PipelineEntity
pipeline_payload = PipelineDetailsEntity
.represent(pipeline, request: req)
.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'
describe PipelineEntity do
let(:user) { create(:user) }
set(:user) { create(:user) }
let(:request) { double('request') }
before do
......@@ -28,8 +28,6 @@ describe PipelineEntity 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
......@@ -55,20 +53,12 @@ describe PipelineEntity do
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
it 'contains retry path' do
expect(subject[:retry_path]).to be_present
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
it 'does not contain retry path' do
expect(subject).not_to have_key(:retry_path)
end
......@@ -87,11 +77,7 @@ describe PipelineEntity do
end
context 'user has ability to cancel pipeline' do
before { project.team << [user, :developer] }
it 'cancelable flag is true' do
expect(subject[:flags][:cancelable]).to eq true
end
before { project.add_developer(user) }
it 'contains cancel path' do
expect(subject[:cancel_path]).to be_present
......@@ -99,42 +85,12 @@ describe PipelineEntity do
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
it 'does not contain cancel path' do
expect(subject).not_to have_key(:cancel_path)
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
let(:pipeline) { create(:ci_empty_pipeline) }
......
......@@ -113,7 +113,7 @@ describe PipelineSerializer do
it "verifies number of queries" do
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)
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
%i[type lock_version target_url base_tags
commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried].freeze
user_id auto_canceled_by_id retried sourced_pipelines].freeze
shared_examples 'build duplication' do
let(:build) do
......
......@@ -42,5 +42,34 @@ describe ExpirePipelineCacheWorker do
subject.perform(pipeline.id)
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
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