Commit eda2229f authored by Jan Beckmann's avatar Jan Beckmann Committed by Mayra Cabrera

Add relationship and link to new epic to promoted issues

Closes #9898
parent 610d9235
...@@ -183,4 +183,4 @@ module IssuesHelper ...@@ -183,4 +183,4 @@ module IssuesHelper
module_function :url_for_tracker_issue module_function :url_for_tracker_issue
end end
IssuesHelper.include_if_ee('EE::IssuesHelper') IssuesHelper.prepend_if_ee('EE::IssuesHelper')
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPromotedToEpicToIssues < ActiveRecord::Migration[5.2]
DOWNTIME = false
def up
add_column :issues, :promoted_to_epic_id, :integer
end
def down
remove_column :issues, :promoted_to_epic_id
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPromotedToEpicToIssuesIndex < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :issues, :epics, column: :promoted_to_epic_id, on_delete: :nullify
add_concurrent_index :issues, :promoted_to_epic_id, where: 'promoted_to_epic_id IS NOT NULL'
end
def down
remove_concurrent_index(:issues, :promoted_to_epic_id)
remove_foreign_key :issues, column: :promoted_to_epic_id
end
end
...@@ -2049,6 +2049,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do ...@@ -2049,6 +2049,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do
t.integer "closed_by_id" t.integer "closed_by_id"
t.integer "state_id", limit: 2, default: 1, null: false t.integer "state_id", limit: 2, default: 1, null: false
t.integer "duplicated_to_id" t.integer "duplicated_to_id"
t.integer "promoted_to_epic_id"
t.index ["author_id"], name: "index_issues_on_author_id" t.index ["author_id"], name: "index_issues_on_author_id"
t.index ["closed_by_id"], name: "index_issues_on_closed_by_id" t.index ["closed_by_id"], name: "index_issues_on_closed_by_id"
t.index ["confidential"], name: "index_issues_on_confidential" t.index ["confidential"], name: "index_issues_on_confidential"
...@@ -2065,6 +2066,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do ...@@ -2065,6 +2066,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do
t.index ["project_id", "relative_position", "state_id", "id"], name: "idx_issues_on_project_id_and_rel_position_and_state_id_and_id", order: { id: :desc } t.index ["project_id", "relative_position", "state_id", "id"], name: "idx_issues_on_project_id_and_rel_position_and_state_id_and_id", order: { id: :desc }
t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state" t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state"
t.index ["project_id", "updated_at", "id", "state_id"], name: "idx_issues_on_project_id_and_updated_at_and_id_and_state_id" t.index ["project_id", "updated_at", "id", "state_id"], name: "idx_issues_on_project_id_and_updated_at_and_id_and_state_id"
t.index ["promoted_to_epic_id"], name: "index_issues_on_promoted_to_epic_id", where: "(promoted_to_epic_id IS NOT NULL)"
t.index ["relative_position"], name: "index_issues_on_relative_position" t.index ["relative_position"], name: "index_issues_on_relative_position"
t.index ["state"], name: "index_issues_on_state" t.index ["state"], name: "index_issues_on_state"
t.index ["state_id"], name: "idx_issues_on_state_id" t.index ["state_id"], name: "idx_issues_on_state_id"
...@@ -4428,6 +4430,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do ...@@ -4428,6 +4430,7 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do
add_foreign_key "issue_tracker_data", "services", on_delete: :cascade add_foreign_key "issue_tracker_data", "services", on_delete: :cascade
add_foreign_key "issue_user_mentions", "issues", on_delete: :cascade add_foreign_key "issue_user_mentions", "issues", on_delete: :cascade
add_foreign_key "issue_user_mentions", "notes", on_delete: :cascade add_foreign_key "issue_user_mentions", "notes", on_delete: :cascade
add_foreign_key "issues", "epics", column: "promoted_to_epic_id", name: "fk_df75a7c8b8", on_delete: :nullify
add_foreign_key "issues", "issues", column: "duplicated_to_id", name: "fk_9c4516d665", on_delete: :nullify add_foreign_key "issues", "issues", column: "duplicated_to_id", name: "fk_9c4516d665", on_delete: :nullify
add_foreign_key "issues", "issues", column: "moved_to_id", name: "fk_a194299be1", on_delete: :nullify add_foreign_key "issues", "issues", column: "moved_to_id", name: "fk_a194299be1", on_delete: :nullify
add_foreign_key "issues", "milestones", name: "fk_96b1dd429c", on_delete: :nullify add_foreign_key "issues", "milestones", name: "fk_96b1dd429c", on_delete: :nullify
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module EE module EE
module IssuesHelper module IssuesHelper
extend ::Gitlab::Utils::Override
def weight_dropdown_tag(issuable, opts = {}, &block) def weight_dropdown_tag(issuable, opts = {}, &block)
title = issuable.weight || 'Weight' title = issuable.weight || 'Weight'
additional_toggle_class = opts.delete(:toggle_class) additional_toggle_class = opts.delete(:toggle_class)
...@@ -28,5 +30,14 @@ module EE ...@@ -28,5 +30,14 @@ module EE
h(weight.presence || 'Weight') h(weight.presence || 'Weight')
end end
end end
override :issue_closed_link
def issue_closed_link(issue, current_user, css_class: '')
if issue.promoted? && can?(current_user, :read_epic, issue.promoted_to_epic)
link_to(s_('IssuableStatus|promoted'), issue.promoted_to_epic, class: css_class)
else
super
end
end
end end
end end
...@@ -26,6 +26,7 @@ module EE ...@@ -26,6 +26,7 @@ module EE
has_one :epic_issue has_one :epic_issue
has_one :epic, through: :epic_issue has_one :epic, through: :epic_issue
belongs_to :promoted_to_epic, class_name: 'Epic'
has_many :designs, class_name: "DesignManagement::Design", inverse_of: :issue has_many :designs, class_name: "DesignManagement::Design", inverse_of: :issue
has_many :design_versions, class_name: "DesignManagement::Version", inverse_of: :issue do has_many :design_versions, class_name: "DesignManagement::Version", inverse_of: :issue do
def most_recent def most_recent
...@@ -124,6 +125,10 @@ module EE ...@@ -124,6 +125,10 @@ module EE
@design_collection ||= ::DesignManagement::DesignCollection.new(self) @design_collection ||= ::DesignManagement::DesignCollection.new(self)
end end
def promoted?
!!promoted_to_epic_id
end
class_methods do class_methods do
# override # override
def sort_by_attribute(method, excluded_labels: []) def sort_by_attribute(method, excluded_labels: [])
......
...@@ -33,6 +33,16 @@ module Epics ...@@ -33,6 +33,16 @@ module Epics
@new_entity = Epics::CreateService.new(@group, current_user, params).execute @new_entity = Epics::CreateService.new(@group, current_user, params).execute
end end
def update_old_entity
super
mark_as_promoted
end
def mark_as_promoted
original_entity.update(promoted_to_epic: new_entity)
end
def params def params
{ {
title: original_entity.title title: original_entity.title
......
---
title: Add link to new epic for promoted issues
merge_request: 18839
author: Jan Beckmann
type: added
# frozen_string_literal: true
require "spec_helper"
describe IssuesHelper do
let(:project) { create(:project) }
let(:issue) { create :issue, project: project }
describe '#issue_closed_link' do
let(:new_epic) { create(:epic) }
let(:user) { create(:user) }
context 'with linked issue' do
context 'with promoted issue' do
before do
issue.update(promoted_to_epic: new_epic)
end
context 'when user has permission to see new epic' do
before do
expect(helper).to receive(:can?).with(user, :read_epic, new_epic) { true }
end
let(:css_class) { 'text-white text-underline' }
it 'returns link' do
link = "<a class=\"#{css_class}\" href=\"/groups/#{new_epic.group.full_path}/-/epics/#{new_epic.iid}\">(promoted)</a>"
expect(helper.issue_closed_link(issue, user, css_class: css_class)).to match(link)
end
end
context 'when user has no permission to see new epic' do
before do
expect(helper).to receive(:can?).with(user, :read_epic, new_epic) { false }
end
it 'returns nil' do
expect(helper.issue_closed_link(issue, user)).to be_nil
end
end
end
end
end
end
...@@ -115,6 +115,7 @@ describe Issue do ...@@ -115,6 +115,7 @@ describe Issue do
it { is_expected.to have_many(:prometheus_alerts) } it { is_expected.to have_many(:prometheus_alerts) }
it { is_expected.to have_many(:vulnerability_links).class_name('Vulnerabilities::IssueLink').inverse_of(:issue) } it { is_expected.to have_many(:vulnerability_links).class_name('Vulnerabilities::IssueLink').inverse_of(:issue) }
it { is_expected.to have_many(:related_vulnerabilities).through(:vulnerability_links).source(:vulnerability) } it { is_expected.to have_many(:related_vulnerabilities).through(:vulnerability_links).source(:vulnerability) }
it { is_expected.to belong_to(:promoted_to_epic).class_name('Epic') }
describe 'versions.most_recent' do describe 'versions.most_recent' do
it 'returns the most recent version' do it 'returns the most recent version' do
...@@ -256,6 +257,22 @@ describe Issue do ...@@ -256,6 +257,22 @@ describe Issue do
end end
end end
describe '#promoted?' do
let(:issue) { create(:issue) }
subject { issue.promoted? }
context 'issue not promoted' do
it { is_expected.to be_falsey }
end
context 'issue promoted' do
let(:promoted_to_epic) { create(:epic) }
let(:issue) { create(:issue, promoted_to_epic: promoted_to_epic) }
it { is_expected.to be_truthy }
end
end
describe 'relative positioning with group boards' do describe 'relative positioning with group boards' do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:board) { create(:board, group: group) } let!(:board) { create(:board, group: group) }
......
...@@ -83,6 +83,11 @@ describe Epics::IssuePromoteService do ...@@ -83,6 +83,11 @@ describe Epics::IssuePromoteService do
it 'closes the original issue' do it 'closes the original issue' do
expect(issue).to be_closed expect(issue).to be_closed
end end
it 'marks the old issue as promoted' do
expect(issue).to be_promoted
expect(issue.promoted_to_epic).to eq(epic)
end
end end
context 'when promoted issue has notes' do context 'when promoted issue has notes' do
......
...@@ -9565,6 +9565,9 @@ msgstr "" ...@@ -9565,6 +9565,9 @@ msgstr ""
msgid "IssuableStatus|moved" msgid "IssuableStatus|moved"
msgstr "" msgstr ""
msgid "IssuableStatus|promoted"
msgstr ""
msgid "Issue" msgid "Issue"
msgstr "" msgstr ""
......
...@@ -15,6 +15,7 @@ issues: ...@@ -15,6 +15,7 @@ issues:
- user_agent_detail - user_agent_detail
- moved_to - moved_to
- duplicated_to - duplicated_to
- promoted_to_epic
- events - events
- merge_requests_closing_issues - merge_requests_closing_issues
- metrics - metrics
......
...@@ -20,6 +20,7 @@ Issue: ...@@ -20,6 +20,7 @@ Issue:
- due_date - due_date
- moved_to_id - moved_to_id
- duplicated_to_id - duplicated_to_id
- promoted_to_epic_id
- lock_version - lock_version
- milestone_id - milestone_id
- weight - weight
......
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