Commit 2d15c330 authored by Toon Claes's avatar Toon Claes

Introduce project_settings table

There is no usable data in this table yet, but with this change the
table is created, it's backfilled for every existing project, and new
rows are created as new projects are created. When the background
migration is complete it should be fairly easy to add columns to this
table and store project settings data in this table instead of in the
projects table itself.

This is a first step toward:
https://gitlab.com/gitlab-org/gitlab/issues/19826
parent 7e3f8440
......@@ -190,6 +190,7 @@ class Project < ApplicationRecord
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
has_one :grafana_integration, inverse_of: :project
has_one :settings, inverse_of: :project, class_name: 'ProjectSettings'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
......
# frozen_string_literal: true
class ProjectSettings < ApplicationRecord
belongs_to :project, inverse_of: :settings
self.primary_key = :project_id
end
......@@ -90,6 +90,7 @@ module Projects
end
@project.track_project_repository
@project.create_settings
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
......
---
title: Introduce project_settings table
merge_request: 19761
author:
type: added
# frozen_string_literal: true
class CreateProjectSettings < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :project_settings, id: false do |t|
t.timestamps_with_timezone null: false
t.references :project, primary_key: true, default: nil, type: :integer, index: false, foreign_key: { on_delete: :cascade }
end
end
end
# frozen_string_literal: true
class BackfillProjectSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'BackfillProjectSettings'
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 10_000
disable_ddl_transaction!
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
end
def up
say "Scheduling `#{MIGRATION}` jobs"
queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
# NOOP
end
end
......@@ -3296,6 +3296,11 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true
end
create_table "project_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
end
create_table "project_statistics", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "namespace_id", null: false
......@@ -4881,6 +4886,7 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
add_foreign_key "project_repositories", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "shards", on_delete: :restrict
add_foreign_key "project_repository_states", "projects", on_delete: :cascade
add_foreign_key "project_settings", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
add_foreign_key "project_tracing_settings", "projects", on_delete: :cascade
add_foreign_key "projects", "pool_repositories", name: "fk_6e5c14658a", on_delete: :nullify
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Backfill project_settings for a range of projects
class BackfillProjectSettings
def perform(start_id, end_id)
ActiveRecord::Base.connection.execute <<~SQL
INSERT INTO project_settings (project_id, created_at, updated_at)
SELECT projects.id, now(), now()
FROM projects
WHERE projects.id BETWEEN #{start_id} AND #{end_id}
ON CONFLICT (project_id) DO NOTHING;
SQL
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::BackfillProjectSettings, :migration, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
let(:project) { projects.create(namespace_id: namespace.id) }
subject { described_class.new }
describe '#perform' do
it 'creates settings for all projects in range' do
projects.create(id: 5, namespace_id: namespace.id)
projects.create(id: 7, namespace_id: namespace.id)
projects.create(id: 8, namespace_id: namespace.id)
subject.perform(5, 7)
expect(project_settings.all.pluck(:project_id)).to contain_exactly(5, 7)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200122123016_backfill_project_settings.rb')
describe BackfillProjectSettings, :migration, :sidekiq, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
let(:project) { projects.create(namespace_id: namespace.id) }
describe '#up' do
before do
stub_const("#{described_class}::BATCH_SIZE", 2)
projects.create(id: 1, namespace_id: namespace.id)
projects.create(id: 2, namespace_id: namespace.id)
projects.create(id: 3, namespace_id: namespace.id)
end
it 'schedules BackfillProjectSettings background jobs' do
Sidekiq::Testing.fake! do
Timecop.freeze do
migrate!
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 3, 3)
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ProjectSettings, type: :model do
it { is_expected.to belong_to(:project) }
end
......@@ -69,6 +69,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_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
it { is_expected.to have_one(:settings).class_name('ProjectSettings') }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:ci_pipelines) }
it { is_expected.to have_many(:builds) }
......
......@@ -43,6 +43,12 @@ describe Projects::CreateService, '#execute' do
create_project(user, opts)
end
it 'creates associated project settings' do
project = create_project(user, opts)
expect(project.settings).to be_persisted
end
end
context "admin creates project with other user's namespace_id" do
......
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