Commit f2a49bb2 authored by Eugenia Grieff's avatar Eugenia Grieff

Create RelatedEpic table and model

Create table to store related epics records
with indexes.
Add RelatedEpic model, factory and tests

Changelog: added
parent f104d053
# frozen_string_literal: true
class CreateRelatedEpicLinks < Gitlab::Database::Migration[1.0]
def up
create_table :related_epic_links do |t|
t.references :source, index: true, foreign_key: { to_table: :epics, on_delete: :cascade }, null: false
t.references :target, index: true, foreign_key: { to_table: :epics, on_delete: :cascade }, null: false
t.integer :link_type, null: false, default: 0, limit: 2
t.timestamps_with_timezone null: false
t.index [:source_id, :target_id], unique: true
end
end
def down
drop_table :related_epic_links
end
end
73feefe409b9c0f4ea373d0c3f13690df0086fbc4fc212595e959ad65fcc27b1
\ No newline at end of file
......@@ -19197,6 +19197,24 @@ CREATE SEQUENCE redirect_routes_id_seq
ALTER SEQUENCE redirect_routes_id_seq OWNED BY redirect_routes.id;
CREATE TABLE related_epic_links (
id bigint NOT NULL,
source_id bigint NOT NULL,
target_id bigint NOT NULL,
link_type smallint DEFAULT 0 NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE related_epic_links_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE related_epic_links_id_seq OWNED BY related_epic_links.id;
CREATE TABLE release_links (
id bigint NOT NULL,
release_id integer NOT NULL,
......@@ -22280,6 +22298,8 @@ ALTER TABLE ONLY raw_usage_data ALTER COLUMN id SET DEFAULT nextval('raw_usage_d
ALTER TABLE ONLY redirect_routes ALTER COLUMN id SET DEFAULT nextval('redirect_routes_id_seq'::regclass);
ALTER TABLE ONLY related_epic_links ALTER COLUMN id SET DEFAULT nextval('related_epic_links_id_seq'::regclass);
ALTER TABLE ONLY release_links ALTER COLUMN id SET DEFAULT nextval('release_links_id_seq'::regclass);
ALTER TABLE ONLY releases ALTER COLUMN id SET DEFAULT nextval('releases_id_seq'::regclass);
......@@ -24204,6 +24224,9 @@ ALTER TABLE ONLY raw_usage_data
ALTER TABLE ONLY redirect_routes
ADD CONSTRAINT redirect_routes_pkey PRIMARY KEY (id);
ALTER TABLE ONLY related_epic_links
ADD CONSTRAINT related_epic_links_pkey PRIMARY KEY (id);
ALTER TABLE ONLY release_links
ADD CONSTRAINT release_links_pkey PRIMARY KEY (id);
......@@ -27616,6 +27639,12 @@ CREATE UNIQUE INDEX index_redirect_routes_on_path_unique_text_pattern_ops ON red
CREATE INDEX index_redirect_routes_on_source_type_and_source_id ON redirect_routes USING btree (source_type, source_id);
CREATE INDEX index_related_epic_links_on_source_id ON related_epic_links USING btree (source_id);
CREATE UNIQUE INDEX index_related_epic_links_on_source_id_and_target_id ON related_epic_links USING btree (source_id, target_id);
CREATE INDEX index_related_epic_links_on_target_id ON related_epic_links USING btree (target_id);
CREATE UNIQUE INDEX index_release_links_on_release_id_and_name ON release_links USING btree (release_id, name);
CREATE UNIQUE INDEX index_release_links_on_release_id_and_url ON release_links USING btree (release_id, url);
......@@ -30239,6 +30268,9 @@ ALTER TABLE ONLY packages_debian_group_distributions
ALTER TABLE ONLY packages_conan_file_metadata
ADD CONSTRAINT fk_rails_0afabd9328 FOREIGN KEY (package_file_id) REFERENCES packages_package_files(id) ON DELETE CASCADE;
ALTER TABLE ONLY related_epic_links
ADD CONSTRAINT fk_rails_0b72027748 FOREIGN KEY (target_id) REFERENCES epics(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_build_pending_states
ADD CONSTRAINT fk_rails_0bbbfeaf9d FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
......@@ -31367,6 +31399,9 @@ ALTER TABLE ONLY group_deploy_keys_groups
ALTER TABLE ONLY merge_request_user_mentions
ADD CONSTRAINT fk_rails_c440b9ea31 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
ALTER TABLE ONLY related_epic_links
ADD CONSTRAINT fk_rails_c464534def FOREIGN KEY (source_id) REFERENCES epics(id) ON DELETE CASCADE;
ALTER TABLE ONLY boards_epic_board_recent_visits
ADD CONSTRAINT fk_rails_c4dcba4a3e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
# frozen_string_literal: true
class Epic::RelatedEpicLink < ApplicationRecord
self.table_name = 'related_epic_links'
belongs_to :source, class_name: 'Epic'
belongs_to :target, class_name: 'Epic'
validates :source, presence: true
validates :target, presence: true
validates :source, uniqueness: { scope: :target_id, message: 'is already related' }
validate :check_self_relation
validate :check_opposite_relation
scope :for_source_epic, ->(epic) { where(source_id: epic.id) }
scope :for_target_epic, ->(epic) { where(target_id: epic.id) }
TYPE_RELATES_TO = 'relates_to'
TYPE_BLOCKS = 'blocks'
TYPE_IS_BLOCKED_BY = 'is_blocked_by'
enum link_type: { TYPE_RELATES_TO => 0, TYPE_BLOCKS => 1 }
private
def check_self_relation
return unless source && target
if source == target
errors.add(:source, 'cannot be related to itself')
end
end
def check_opposite_relation
return unless source && target
if Epic::RelatedEpicLink.find_by(source: target, target: source)
errors.add(:source, 'is already related to this epic')
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :related_epic_link, class: 'Epic::RelatedEpicLink' do
source factory: :epic
target factory: :epic
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Epic::RelatedEpicLink do
describe 'Associations' do
it { is_expected.to belong_to(:source).class_name('Epic') }
it { is_expected.to belong_to(:target).class_name('Epic') }
end
describe 'link_type' do
it { is_expected.to define_enum_for(:link_type).with_values(relates_to: 0, blocks: 1) }
it 'provides the "related" as default link_type' do
expect(create(:related_epic_link).link_type).to eq 'relates_to'
end
end
describe 'Validation' do
subject { create :related_epic_link }
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:target) }
it do
is_expected.to validate_uniqueness_of(:source)
.scoped_to(:target_id)
.with_message(/already related/)
end
it 'is not valid if an opposite link already exists' do
related_epic_link = build(:related_epic_link, source: subject.target, target: subject.source)
expect(related_epic_link).to be_invalid
expect(related_epic_link.errors[:source]).to include('is already related to this epic')
end
context 'when it relates to itself' do
let(:epic) { create :epic }
context 'cannot be validated' do
it 'does not invalidate object with self relation error' do
related_epic_link = build(:related_epic_link, source: epic, target: nil)
related_epic_link.valid?
expect(related_epic_link.errors[:source]).to be_empty
end
end
context 'can be invalidated' do
it 'invalidates object' do
related_epic_link = build(:related_epic_link, source: epic, target: epic)
expect(related_epic_link).to be_invalid
expect(related_epic_link.errors[:source]).to include('cannot be related to itself')
end
end
end
end
describe 'Scopes' do
let_it_be(:epic1) { create(:epic) }
let_it_be(:epic2) { create(:epic) }
describe '.for_source_epic' do
it 'includes related epics for source epic' do
source_epic = create(:epic)
related_epic_link_1 = create(:related_epic_link, source: source_epic, target: epic1)
related_epic_link_2 = create(:related_epic_link, source: source_epic, target: epic2)
result = described_class.for_source_epic(source_epic)
expect(result).to contain_exactly(related_epic_link_1, related_epic_link_2)
end
end
describe '.for_target_epic' do
it 'includes related epics for target epic' do
target_epic = create(:epic)
related_epic_link_1 = create(:related_epic_link, source: epic1, target: target_epic)
related_epic_link_2 = create(:related_epic_link, source: epic2, target: target_epic)
result = described_class.for_target_epic(target_epic)
expect(result).to contain_exactly(related_epic_link_1, related_epic_link_2)
end
end
end
end
......@@ -440,6 +440,7 @@ push_event_payloads: :gitlab_main
push_rules: :gitlab_main
raw_usage_data: :gitlab_main
redirect_routes: :gitlab_main
related_epic_links: :gitlab_main
release_links: :gitlab_main
releases: :gitlab_main
remote_mirrors: :gitlab_main
......
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