Commit a6778fc6 authored by Francisco Javier López's avatar Francisco Javier López 🔴 Committed by Grzegorz Bizon

Rename project's pipelines relation

parent 107ff10e
......@@ -22,7 +22,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Fetch branches for the specified mode
fetch_branches_by_mode
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
# n+1: https://gitlab.com/gitlab-org/gitaly/issues/992
......
......@@ -46,7 +46,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def new
@pipeline = project.pipelines.new(ref: @project.default_branch)
@pipeline = project.all_pipelines.new(ref: @project.default_branch)
end
def create
......@@ -142,9 +142,9 @@ class Projects::PipelinesController < Projects::ApplicationController
@charts[:pipeline_times] = Gitlab::Ci::Charts::PipelineTime.new(project)
@counts = {}
@counts[:total] = @project.pipelines.count(:all)
@counts[:success] = @project.pipelines.success.count(:all)
@counts[:failed] = @project.pipelines.failed.count(:all)
@counts[:total] = @project.all_pipelines.count(:all)
@counts[:success] = @project.all_pipelines.success.count(:all)
@counts[:failed] = @project.all_pipelines.failed.count(:all)
end
private
......@@ -164,7 +164,7 @@ class Projects::PipelinesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def pipeline
@pipeline ||= project
.pipelines
.all_pipelines
.includes(user: :status)
.find_by!(id: params[:id])
.present(current_user: current_user)
......
......@@ -20,7 +20,7 @@ class Projects::TagsController < Projects::ApplicationController
@tags = Kaminari.paginate_array(@tags).page(params[:page])
tag_names = @tags.map(&:name)
@tags_pipelines = @project.pipelines.latest_successful_for_refs(tag_names)
@tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names)
@releases = project.releases.where(tag: tag_names)
respond_to do |format|
......
......@@ -8,7 +8,7 @@ class PipelinesFinder
def initialize(project, current_user, params = {})
@project = project
@current_user = current_user
@pipelines = project.pipelines
@pipelines = project.all_pipelines
@params = params
end
......
......@@ -12,14 +12,14 @@ module Ci
include AtomicInternalId
include EnumWithNil
belongs_to :project, inverse_of: :pipelines
belongs_to :project, inverse_of: :all_pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
belongs_to :merge_request, class_name: 'MergeRequest'
has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count
s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count
end
has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline
......@@ -174,6 +174,7 @@ module Ci
end
scope :internal, -> { where(source: internal_sources) }
scope :ci_sources, -> { where(config_source: ci_sources_values) }
scope :sort_by_merge_request_pipelines, -> do
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
......@@ -271,6 +272,10 @@ module Ci
sources.reject { |source| source == "external" }.values
end
def self.ci_sources_values
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end
def stages_count
statuses.select(:stage).distinct.count
end
......
......@@ -298,7 +298,7 @@ class Commit
end
def pipelines
project.pipelines.where(sha: sha)
project.ci_pipelines.where(sha: sha)
end
def last_pipeline
......@@ -312,7 +312,7 @@ class Commit
end
def status_for_project(ref, pipeline_project)
pipeline_project.pipelines.latest_status_per_commit(id, ref)[id]
pipeline_project.ci_pipelines.latest_status_per_commit(id, ref)[id]
end
def set_status_for_ref(ref, status)
......
......@@ -24,7 +24,7 @@ class CommitCollection
# Setting this status ahead of time removes the need for running a query for
# every commit we're displaying.
def with_pipeline_status
statuses = project.pipelines.latest_status_per_commit(map(&:id), ref)
statuses = project.ci_pipelines.latest_status_per_commit(map(&:id), ref)
each do |commit|
commit.set_status_for_ref(ref, statuses[commit.id])
......
......@@ -1056,7 +1056,7 @@ class MergeRequest < ActiveRecord::Base
def all_pipelines(shas: all_commit_shas)
return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines
@all_pipelines ||= source_project.ci_pipelines
.where(sha: shas, ref: source_branch)
.where(merge_request: [nil, self])
.sort_by_merge_request_pipelines
......@@ -1220,7 +1220,7 @@ class MergeRequest < ActiveRecord::Base
end
def base_pipeline
@base_pipeline ||= project.pipelines
@base_pipeline ||= project.ci_pipelines
.order(id: :desc)
.find_by(sha: diff_base_sha)
end
......
......@@ -247,7 +247,17 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses
has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# The relation :all_pipelines is intented to be used when we want to get the
# whole list of pipelines associated to the project
has_many :all_pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# The relation :ci_pipelines is intented to be used when we want to get only
# those pipeline which are directly related to CI. There are
# other pipelines, like webide ones, that we won't retrieve
# if we use this relation.
has_many :ci_pipelines,
-> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all },
class_name: 'Ci::Pipeline',
inverse_of: :project
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and
......@@ -621,7 +631,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch)
latest_pipeline = pipelines.latest_successful_for(ref)
latest_pipeline = ci_pipelines.latest_successful_for(ref)
if latest_pipeline
latest_pipeline.builds.latest.with_artifacts_archive
......@@ -1376,7 +1386,7 @@ class Project < ActiveRecord::Base
return unless sha
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
def latest_successful_pipeline_for_default_branch
......@@ -1385,12 +1395,12 @@ class Project < ActiveRecord::Base
end
@latest_successful_pipeline_for_default_branch =
pipelines.latest_successful_for(default_branch)
ci_pipelines.latest_successful_for(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
pipelines.latest_successful_for(ref)
ci_pipelines.latest_successful_for(ref)
else
latest_successful_pipeline_for_default_branch
end
......
......@@ -38,11 +38,11 @@ class PipelinesEmailService < Service
end
def can_test?
project.pipelines.any?
project.ci_pipelines.any?
end
def test_data(project, user)
data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last)
data = Gitlab::DataBuilder::Pipeline.build(project.ci_pipelines.last)
data[:user] = user.hook_attrs
data
end
......
......@@ -78,7 +78,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def auto_cancelable_pipelines
project.pipelines
project.ci_pipelines
.where(ref: pipeline.ref)
.where.not(id: pipeline.id)
.where.not(sha: project.commit(pipeline.ref).try(:id))
......
......@@ -34,7 +34,7 @@ module Projects
end
def auto_devops_pipelines
@auto_devops_pipelines ||= project.pipelines.auto_devops_source
@auto_devops_pipelines ||= project.ci_pipelines.auto_devops_source
end
end
end
......
......@@ -49,7 +49,7 @@ module TestHooks
end
def pipeline_events_data
pipeline = project.pipelines.first
pipeline = project.ci_pipelines.first
throw(:validation_error, 'Ensure the project has CI pipelines.') unless pipeline.present?
Gitlab::DataBuilder::Pipeline.build(pipeline)
......
......@@ -104,7 +104,7 @@ class Gitlab::Seeder::Pipelines
def create_pipeline!(project, ref, commit)
project.pipelines.create!(sha: commit.id, ref: ref, source: :push)
project.ci_pipelines.create!(sha: commit.id, ref: ref, source: :push)
end
def build_create!(pipeline, opts = {})
......
......@@ -29,7 +29,7 @@ module API
not_found!('Commit') unless user_project.commit(params[:sha])
pipelines = user_project.pipelines.where(sha: params[:sha])
pipelines = user_project.ci_pipelines.where(sha: params[:sha])
statuses = ::CommitStatus.where(pipeline: pipelines)
statuses = statuses.latest unless to_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
......@@ -75,7 +75,7 @@ module API
pipeline = @project.pipeline_for(ref, commit.sha)
unless pipeline
pipeline = @project.pipelines.create!(
pipeline = @project.ci_pipelines.create!(
source: :external,
sha: commit.sha,
ref: ref,
......
......@@ -56,7 +56,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/pipelines/:pipeline_id/jobs' do
pipeline = user_project.pipelines.find(params[:pipeline_id])
pipeline = user_project.ci_pipelines.find(params[:pipeline_id])
builds = pipeline.builds
builds = filter_builds(builds, params[:scope])
builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace])
......
......@@ -130,7 +130,7 @@ module API
helpers do
def pipeline
@pipeline ||= user_project.pipelines.find(params[:pipeline_id])
@pipeline ||= user_project.ci_pipelines.find(params[:pipeline_id])
end
end
end
......
......@@ -14,7 +14,7 @@ module Gitlab
@ref = ref
@job = job
@pipeline = @project.pipelines.latest_successful_for(@ref)
@pipeline = @project.ci_pipelines.latest_successful_for(@ref)
end
def entity
......
......@@ -22,7 +22,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def status
@project.pipelines
@project.ci_pipelines
.where(sha: @sha)
.latest_status(@ref) || 'unknown'
end
......
......@@ -54,7 +54,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def collect
query = project.pipelines
query = project.all_pipelines
.where("? > #{::Ci::Pipeline.table_name}.created_at AND #{::Ci::Pipeline.table_name}.created_at > ?", @to, @from) # rubocop:disable GitlabSecurity/SqlInjection
totals_count = grouped_count(query)
......@@ -115,7 +115,7 @@ module Gitlab
class PipelineTime < Chart
def collect
commits = project.pipelines.last(30)
commits = project.all_pipelines.last(30)
commits.each do |commit|
@labels << commit.short_sha
......
......@@ -51,7 +51,7 @@ project_tree:
- resource_label_events:
- label:
:priorities
- pipelines:
- ci_pipelines:
- notes:
- :author
- events:
......
......@@ -26,6 +26,8 @@ module Gitlab
@project_members = @tree_hash.delete('project_members')
RelationRenameService.rename(@tree_hash)
ActiveRecord::Base.uncached do
ActiveRecord::Base.no_touching do
create_relations
......@@ -214,7 +216,7 @@ module Gitlab
end
def nil_iid_pipeline?(relation_key, relation_item)
relation_key == 'pipelines' && relation_item['iid'].nil?
relation_key == 'ci_pipelines' && relation_item['iid'].nil?
end
end
end
......
......@@ -34,6 +34,8 @@ module Gitlab
project_json['project_members'] += group_members_json
RelationRenameService.add_new_associations(project_json)
project_json.to_json
end
......
......@@ -4,6 +4,7 @@ module Gitlab
module ImportExport
class RelationFactory
OVERRIDES = { snippets: :project_snippets,
ci_pipelines: 'Ci::Pipeline',
pipelines: 'Ci::Pipeline',
stages: 'Ci::Stage',
statuses: 'commit_status',
......
# frozen_string_literal: true
# This class is intended to help with relation renames within Gitlab versions
# and allow compatibility between versions.
# If you have to change one relationship name that is imported/exported,
# you should add it to the RENAMES constant indicating the old name and the
# new one.
# The behavior of these renamed relationships should be transient and it should
# only last one release until you completely remove the renaming from the list.
#
# When importing, this class will check the project hash and:
# - if only the old relationship name is found, it will rename it with the new one
# - if only the new relationship name is found, it will do nothing
# - if it finds both, it will use the new relationship data
#
# When exporting, this class will duplicate the keys in the resulting file.
# This way, if we open the file in an old version of the exporter it will work
# and also it will with the newer versions.
module Gitlab
module ImportExport
class RelationRenameService
RENAMES = {
'pipelines' => 'ci_pipelines' # Added in 11.6, remove in 11.7
}.freeze
def self.rename(tree_hash)
return unless tree_hash&.present?
RENAMES.each do |old_name, new_name|
old_entry = tree_hash.delete(old_name)
next if tree_hash[new_name]
next unless old_entry
tree_hash[new_name] = old_entry
end
end
def self.add_new_associations(tree_hash)
RENAMES.each do |old_name, new_name|
next if tree_hash.key?(old_name)
tree_hash[old_name] = tree_hash[new_name]
end
end
end
end
end
......@@ -20,7 +20,7 @@ describe 'project commit pipelines', :js do
visit pipelines_project_commit_path(project, project.commit.sha)
page.within('.table-holder') do
expect(page).to have_content project.pipelines[0].id # pipeline ids
expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
end
end
end
......
......@@ -300,7 +300,7 @@ describe 'Pages' do
let(:pipeline) do
commit_sha = project.commit('HEAD').sha
project.pipelines.create(
project.ci_pipelines.create(
ref: 'HEAD',
sha: commit_sha,
source: :push,
......
......@@ -103,7 +103,7 @@ merge_request_diff_commits:
- merge_request_diff
merge_request_diff_files:
- merge_request_diff
pipelines:
ci_pipelines:
- project
- user
- stages
......@@ -265,7 +265,8 @@ project:
- notification_settings
- import_data
- commit_statuses
- pipelines
- ci_pipelines
- all_pipelines
- stages
- builds
- runner_projects
......
......@@ -197,9 +197,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'has the correct number of pipelines and statuses' do
expect(@project.pipelines.size).to eq(5)
expect(@project.ci_pipelines.size).to eq(5)
@project.pipelines.zip([2, 2, 2, 2, 2])
@project.ci_pipelines.zip([2, 2, 2, 2, 2])
.each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size)
end
......
......@@ -119,16 +119,16 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
it 'has pipeline stages' do
expect(saved_project_json.dig('pipelines', 0, 'stages')).not_to be_empty
expect(saved_project_json.dig('ci_pipelines', 0, 'stages')).not_to be_empty
end
it 'has pipeline statuses' do
expect(saved_project_json.dig('pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty
expect(saved_project_json.dig('ci_pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty
end
it 'has pipeline builds' do
builds_count = saved_project_json
.dig('pipelines', 0, 'stages', 0, 'statuses')
.dig('ci_pipelines', 0, 'stages', 0, 'statuses')
.count { |hash| hash['type'] == 'Ci::Build' }
expect(builds_count).to eq(1)
......@@ -142,11 +142,11 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
it 'has pipeline commits' do
expect(saved_project_json['pipelines']).not_to be_empty
expect(saved_project_json['ci_pipelines']).not_to be_empty
end
it 'has ci pipeline notes' do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
expect(saved_project_json['ci_pipelines'].first['notes']).not_to be_empty
end
it 'has labels with no associations' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::ImportExport::RelationRenameService do
let(:renames) do
{
'example_relation1' => 'new_example_relation1',
'example_relation2' => 'new_example_relation2'
}
end
let(:user) { create(:admin) }
let(:group) { create(:group, :nested) }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:shared) { project.import_export_shared }
before do
stub_const("#{described_class}::RENAMES", renames)
end
context 'when importing' do
let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) }
let(:import_path) { 'spec/lib/gitlab/import_export' }
let(:file_content) { IO.read("#{import_path}/project.json") }
let!(:json_file) { ActiveSupport::JSON.decode(file_content) }
let(:tree_hash) { project_tree_restorer.instance_variable_get(:@tree_hash) }
before do
allow(shared).to receive(:export_path).and_return(import_path)
allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file)
end
context 'when the file has only old relationship names' do
# Configuring the json as an old version exported file, with only
# the previous association with the old name
before do
renames.each do |old_name, _|
json_file[old_name.to_s] = []
end
end
it 'renames old relationships to the new name' do
expect(json_file.keys).to include(*renames.keys)
project_tree_restorer.restore
expect(json_file.keys).to include(*renames.values)
expect(json_file.keys).not_to include(*renames.keys)
end
end
context 'when the file has both the old and new relationships' do
# Configuring the json as the new version exported file, with both
# the old association name and the new one
before do
renames.each do |old_name, new_name|
json_file[old_name.to_s] = [1]
json_file[new_name.to_s] = [2]
end
end
it 'uses the new relationships and removes the old ones from the hash' do
expect(json_file.keys).to include(*renames.keys)
project_tree_restorer.restore
expect(json_file.keys).to include(*renames.values)
expect(json_file.values_at(*renames.values).flatten.uniq.first).to eq 2
expect(json_file.keys).not_to include(*renames.keys)
end
end
context 'when the file has only new relationship names' do
# Configuring the json as the future version exported file, with only
# the new association name
before do
renames.each do |_, new_name|
json_file[new_name.to_s] = []
end
end
it 'uses the new relationships' do
expect(json_file.keys).not_to include(*renames.keys)
project_tree_restorer.restore
expect(json_file.keys).to include(*renames.values)
end
end
end
context 'when exporting' do
let(:project_tree_saver) { Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: user, shared: shared) }
let(:project_tree) { project_tree_saver.send(:project_json) }
it 'adds old relationships to the exported file' do
project_tree.merge!(renames.values.map { |new_name| [new_name, []] }.to_h)
allow(project_tree_saver).to receive(:save) do |arg|
project_tree_saver.send(:project_json_tree)
end
result = project_tree_saver.save
saved_data = ActiveSupport::JSON.decode(result)
expect(saved_data.keys).to include(*(renames.keys + renames.values))
end
end
end
......@@ -33,8 +33,9 @@ describe Ci::Pipeline, :mailer do
describe 'associations' do
it 'has a bidirectional relationship with projects' do
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines)
expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project)
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:all_pipelines)
expect(Project.reflect_on_association(:all_pipelines).has_inverse?).to eq(:project)
expect(Project.reflect_on_association(:ci_pipelines).has_inverse?).to eq(:project)
end
end
......@@ -1186,7 +1187,7 @@ describe Ci::Pipeline, :mailer do
create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop')
create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop')
pipelines = project.pipelines.to_a
pipelines = project.ci_pipelines.to_a
pipelines.each(&:number_of_warnings)
......
......@@ -63,7 +63,7 @@ describe Project do
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:pipelines) }
it { is_expected.to have_many(:ci_pipelines) }
it { is_expected.to have_many(:builds) }