Commit eb021719 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'epics-conf-db' into 'master'

Add epic confidentiality attribute and model validations

See merge request gitlab-org/gitlab!28428
parents 5841b50c 9e63bc43
# frozen_string_literal: true
class AddConfidentialAttributeToEpics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:epics, :confidential, :boolean, default: false)
end
def down
remove_column(:epics, :confidential)
end
end
...@@ -2271,6 +2271,7 @@ CREATE TABLE public.epics ( ...@@ -2271,6 +2271,7 @@ CREATE TABLE public.epics (
state_id smallint DEFAULT 1 NOT NULL, state_id smallint DEFAULT 1 NOT NULL,
start_date_sourcing_epic_id integer, start_date_sourcing_epic_id integer,
due_date_sourcing_epic_id integer, due_date_sourcing_epic_id integer,
confidential boolean DEFAULT false NOT NULL,
external_key character varying(255) external_key character varying(255)
); );
...@@ -12925,6 +12926,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -12925,6 +12926,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200326144443 20200326144443
20200326145443 20200326145443
20200330074719 20200330074719
20200330121000
20200330132913 20200330132913
20200331220930 20200331220930
\. \.
......
...@@ -55,6 +55,8 @@ module EE ...@@ -55,6 +55,8 @@ module EE
validates :group, presence: true validates :group, presence: true
validate :validate_parent, on: :create validate :validate_parent, on: :create
validate :validate_confidential_issues_and_subepics
validate :validate_confidential_parent
alias_attribute :parent_ids, :parent_id alias_attribute :parent_ids, :parent_id
alias_method :issuing_parent, :group alias_method :issuing_parent, :group
...@@ -67,6 +69,7 @@ module EE ...@@ -67,6 +69,7 @@ module EE
scope :in_issues, -> (issues) { joins(:epic_issues).where(epic_issues: { issue_id: issues }).distinct } scope :in_issues, -> (issues) { joins(:epic_issues).where(epic_issues: { issue_id: issues }).distinct }
scope :has_parent, -> { where.not(parent_id: nil) } scope :has_parent, -> { where.not(parent_id: nil) }
scope :iid_starts_with, -> (query) { where("CAST(iid AS VARCHAR) LIKE ?", "#{sanitize_sql_like(query)}%") } scope :iid_starts_with, -> (query) { where("CAST(iid AS VARCHAR) LIKE ?", "#{sanitize_sql_like(query)}%") }
scope :public_only, -> { where(confidential: false) }
scope :within_timeframe, -> (start_date, end_date) do scope :within_timeframe, -> (start_date, end_date) do
where('start_date is not NULL or end_date is not NULL') where('start_date is not NULL or end_date is not NULL')
...@@ -385,5 +388,25 @@ module EE ...@@ -385,5 +388,25 @@ module EE
hierarchy.base_and_ancestors(hierarchy_order: :asc) hierarchy.base_and_ancestors(hierarchy_order: :asc)
end end
def validate_confidential_issues_and_subepics
return unless confidential?
if issues.public_only.any?
errors.add :confidential, _('Cannot make epic confidential if it contains not-confidential issues')
end
if children.public_only.any?
errors.add :confidential, _('Cannot make epic confidential if it contains not-confidential sub-epics')
end
end
def validate_confidential_parent
return unless parent
if !confidential? && parent.confidential?
errors.add :confidential, _('Not-confidential epic cannot be assigned to a confidential parent epic')
end
end
end end
end end
...@@ -45,6 +45,7 @@ module EE ...@@ -45,6 +45,7 @@ module EE
has_many :related_vulnerabilities, through: :vulnerability_links, source: :vulnerability has_many :related_vulnerabilities, through: :vulnerability_links, source: :vulnerability
validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 } validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 }
validate :validate_confidential_epic
after_create :update_generic_alert_title, if: :generic_alert_with_default_title? after_create :update_generic_alert_title, if: :generic_alert_with_default_title?
end end
...@@ -223,5 +224,13 @@ module EE ...@@ -223,5 +224,13 @@ module EE
project.alerts_service_activated? && project.alerts_service_activated? &&
author == ::User.alert_bot author == ::User.alert_bot
end end
def validate_confidential_epic
return unless epic
if !confidential? && epic.confidential?
errors.add :issue, _('Cannot set confidential epic for not-confidential issue')
end
end
end end
end end
...@@ -15,4 +15,16 @@ class EpicIssue < ApplicationRecord ...@@ -15,4 +15,16 @@ class EpicIssue < ApplicationRecord
scope :in_epic, ->(epic_id) { where(epic_id: epic_id) } scope :in_epic, ->(epic_id) { where(epic_id: epic_id) }
scope :related_issues_for_batches, ->(epic_ids) { select(:id, :relative_position).where(epic_id: epic_ids) } scope :related_issues_for_batches, ->(epic_ids) { select(:id, :relative_position).where(epic_id: epic_ids) }
validate :validate_confidential_epic
private
def validate_confidential_epic
return unless epic && issue
if epic.confidential? && !issue.confidential?
errors.add :issue, _('Cannot set confidential epic for not-confidential issue')
end
end
end end
---
title: Added confidential column to epics table
merge_request: 28428
author:
type: added
...@@ -15,6 +15,10 @@ FactoryBot.define do ...@@ -15,6 +15,10 @@ FactoryBot.define do
due_date_is_fixed { true } due_date_is_fixed { true }
end end
trait :confidential do
confidential { true }
end
trait :opened do trait :opened do
state { :opened } state { :opened }
end end
......
...@@ -3,6 +3,29 @@ ...@@ -3,6 +3,29 @@
require 'spec_helper' require 'spec_helper'
describe EpicIssue do describe EpicIssue do
describe 'validations' do
let(:epic) { build(:epic) }
let(:confidential_epic) { build(:epic, :confidential) }
let(:issue) { build(:issue) }
let(:confidential_issue) { build(:issue, :confidential) }
it 'is valid to add not-confidential issue to not-confidential epic' do
expect(build(:epic_issue, epic: epic, issue: issue)).to be_valid
end
it 'is valid to add confidential issue to confidential epic' do
expect(build(:epic_issue, epic: confidential_epic, issue: confidential_issue)).to be_valid
end
it 'is valid to add confidential issue to not-confidential epic' do
expect(build(:epic_issue, epic: epic, issue: confidential_issue)).to be_valid
end
it 'is not valid to add not-confidential issue to confidential epic' do
expect(build(:epic_issue, epic: confidential_epic, issue: issue)).not_to be_valid
end
end
context "relative positioning" do context "relative positioning" do
it_behaves_like "a class that supports relative positioning" do it_behaves_like "a class that supports relative positioning" do
let(:epic) { create(:epic) } let(:epic) { create(:epic) }
......
...@@ -19,6 +19,17 @@ describe Epic do ...@@ -19,6 +19,17 @@ describe Epic do
it { is_expected.to have_many(:user_mentions).class_name("EpicUserMention") } it { is_expected.to have_many(:user_mentions).class_name("EpicUserMention") }
end end
describe 'scopes' do
describe '.public_only' do
it 'only returns public epics' do
public_epic = create(:epic)
create(:epic, confidential: true)
expect(described_class.public_only).to eq([public_epic])
end
end
end
describe 'validations' do describe 'validations' do
subject { build(:epic) } subject { build(:epic) }
...@@ -37,6 +48,41 @@ describe Epic do ...@@ -37,6 +48,41 @@ describe Epic do
expect(epic).not_to be_valid expect(epic).not_to be_valid
end end
it 'is valid if epic is confidential and has only confidential issues' do
issue = create(:issue, :confidential)
epic = create(:epic_issue, issue: issue).epic
epic.confidential = true
expect(epic).to be_valid
end
it 'is not valid if epic is confidential and has not-confidential issues' do
epic = create(:epic_issue).epic
epic.confidential = true
expect(epic).not_to be_valid
end
it 'is valid if epic is confidential and has only confidential subepics' do
epic = create(:epic, group: group)
create(:epic, :confidential, parent: epic, group: group)
epic.confidential = true
expect(epic).to be_valid
end
it 'is not valid if epic is confidential and has not-confidential subepics' do
epic = create(:epic, group: group)
create(:epic, parent: epic, group: group)
epic.confidential = true
expect(epic).not_to be_valid
end
end end
describe 'modules' do describe 'modules' do
......
...@@ -105,9 +105,9 @@ describe Issue do ...@@ -105,9 +105,9 @@ describe Issue do
end end
describe 'validations' do describe 'validations' do
subject { build(:issue) }
describe 'weight' do describe 'weight' do
subject { build(:issue) }
it 'is not valid when negative number' do it 'is not valid when negative number' do
subject.weight = -1 subject.weight = -1
...@@ -125,6 +125,26 @@ describe Issue do ...@@ -125,6 +125,26 @@ describe Issue do
expect(subject).to be_valid expect(subject).to be_valid
end end
end end
describe 'confidential' do
subject { build(:issue, :confidential) }
it 'is valid when changing to not-confidential and is associated with not-confidential epic' do
subject.epic = build(:epic)
subject.confidential = false
expect(subject).to be_valid
end
it 'is not valid when changing to not-confidential and is associated with confidential epic' do
subject.epic = build(:epic, :confidential)
subject.confidential = false
expect(subject).not_to be_valid
end
end
end end
describe 'relations' do describe 'relations' do
......
...@@ -50,7 +50,7 @@ describe 'Add an issue to an Epic' do ...@@ -50,7 +50,7 @@ describe 'Add an issue to an Epic' do
expect(response['errors']).to be_empty expect(response['errors']).to be_empty
expect(response['epicIssue']['iid']).to eq(issue.iid.to_s) expect(response['epicIssue']['iid']).to eq(issue.iid.to_s)
expect(response['epicIssue']['epic']['iid']).to eq(epic.iid.to_s) expect(response['epicIssue']['epic']['iid']).to eq(epic.iid.to_s)
expect(issue.epic).to eq(epic) expect(issue.reload.epic).to eq(epic)
end end
end end
......
...@@ -239,6 +239,7 @@ describe EpicIssues::CreateService do ...@@ -239,6 +239,7 @@ describe EpicIssues::CreateService do
before do before do
group.add_developer(user) group.add_developer(user)
create(:epic_issue, epic: epic, issue: issue) create(:epic_issue, epic: epic, issue: issue)
issue.reload
end end
let(:another_epic) { create(:epic, group: group) } let(:another_epic) { create(:epic, group: group) }
......
...@@ -3353,6 +3353,12 @@ msgstr "" ...@@ -3353,6 +3353,12 @@ msgstr ""
msgid "Cannot create the abuse report. This user has been blocked." msgid "Cannot create the abuse report. This user has been blocked."
msgstr "" msgstr ""
msgid "Cannot make epic confidential if it contains not-confidential issues"
msgstr ""
msgid "Cannot make epic confidential if it contains not-confidential sub-epics"
msgstr ""
msgid "Cannot merge" msgid "Cannot merge"
msgstr "" msgstr ""
...@@ -3371,6 +3377,9 @@ msgstr "" ...@@ -3371,6 +3377,9 @@ msgstr ""
msgid "Cannot refer to a group milestone by an internal id!" msgid "Cannot refer to a group milestone by an internal id!"
msgstr "" msgstr ""
msgid "Cannot set confidential epic for not-confidential issue"
msgstr ""
msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above." msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
msgstr "" msgstr ""
...@@ -13604,6 +13613,9 @@ msgstr "" ...@@ -13604,6 +13613,9 @@ msgstr ""
msgid "Not started" msgid "Not started"
msgstr "" msgstr ""
msgid "Not-confidential epic cannot be assigned to a confidential parent epic"
msgstr ""
msgid "Note" msgid "Note"
msgstr "" msgstr ""
......
...@@ -845,6 +845,7 @@ Epic: ...@@ -845,6 +845,7 @@ Epic:
- due_date_sourcing_epic_id - due_date_sourcing_epic_id
- health_status - health_status
- external_key - external_key
- confidential
EpicIssue: EpicIssue:
- id - id
- relative_position - relative_position
......
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