Merge branch 'issue_44821' into 'master'
Retrieve merge request closing issues from database cache Closes #44821 See merge request gitlab-org/gitlab-ce!20911
This commit is contained in:
commit
b4415c0174
15 changed files with 112 additions and 12 deletions
|
@ -60,6 +60,8 @@ class MergeRequest < ActiveRecord::Base
|
|||
class_name: 'MergeRequestsClosingIssues',
|
||||
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
|
||||
|
||||
belongs_to :assignee, class_name: "User"
|
||||
|
||||
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
@ -763,8 +765,9 @@ class MergeRequest < ActiveRecord::Base
|
|||
# Calculating this information for a number of merge requests requires
|
||||
# running `ReferenceExtractor` on each of them separately.
|
||||
# This optimization does not apply to issues from external sources.
|
||||
def cache_merge_request_closes_issues!(current_user)
|
||||
def cache_merge_request_closes_issues!(current_user = self.author)
|
||||
return unless project.issues_enabled?
|
||||
return if closed? || merged?
|
||||
|
||||
transaction do
|
||||
self.merge_requests_closing_issues.delete_all
|
||||
|
@ -777,6 +780,18 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def visible_closing_issues_for(current_user = self.author)
|
||||
strong_memoize(:visible_closing_issues_for) do
|
||||
if self.target_project.has_external_issue_tracker?
|
||||
closes_issues(current_user)
|
||||
else
|
||||
cached_closes_issues.select do |issue|
|
||||
Ability.allowed?(current_user, :read_issue, issue)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the set of issues that will be closed if this merge request is accepted.
|
||||
def closes_issues(current_user = self.author)
|
||||
if target_branch == project.default_branch
|
||||
|
@ -796,7 +811,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
ext = Gitlab::ReferenceExtractor.new(project, current_user)
|
||||
ext.analyze("#{title}\n#{description}")
|
||||
|
||||
ext.issues - closes_issues(current_user)
|
||||
ext.issues - visible_closing_issues_for(current_user)
|
||||
end
|
||||
|
||||
def target_project_path
|
||||
|
@ -844,7 +859,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def merge_commit_message(include_description: false)
|
||||
closes_issues_references = closes_issues.map do |issue|
|
||||
closes_issues_references = visible_closing_issues_for.map do |issue|
|
||||
issue.to_reference(target_project)
|
||||
end
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def closing_issues
|
||||
@closing_issues ||= closes_issues(current_user)
|
||||
@closing_issues ||= visible_closing_issues_for(current_user)
|
||||
end
|
||||
|
||||
def pipeline
|
||||
|
|
|
@ -25,7 +25,7 @@ module MergeRequests
|
|||
def close_issues(merge_request)
|
||||
return unless merge_request.target_branch == project.default_branch
|
||||
|
||||
closed_issues = merge_request.closes_issues(current_user)
|
||||
closed_issues = merge_request.visible_closing_issues_for(current_user)
|
||||
|
||||
closed_issues.each do |issue|
|
||||
if can?(current_user, :update_issue, issue)
|
||||
|
|
|
@ -14,6 +14,7 @@ module MergeRequests
|
|||
merge_request.mark_as_unchecked
|
||||
invalidate_cache_counts(merge_request, users: merge_request.assignees)
|
||||
merge_request.update_project_counter_caches
|
||||
merge_request.cache_merge_request_closes_issues!(current_user)
|
||||
end
|
||||
|
||||
merge_request
|
||||
|
|
5
changelogs/unreleased/issue_44821.yml
Normal file
5
changelogs/unreleased/issue_44821.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Retrieve merge request closing issues from database cache
|
||||
merge_request: 20911
|
||||
author:
|
||||
type: fixed
|
|
@ -380,7 +380,7 @@ module API
|
|||
end
|
||||
get ':id/merge_requests/:merge_request_iid/closes_issues' do
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
|
||||
issues = ::Kaminari.paginate_array(merge_request.visible_closing_issues_for(current_user))
|
||||
issues = paginate(issues)
|
||||
|
||||
external_issues, internal_issues = issues.partition { |issue| issue.is_a?(ExternalIssue) }
|
||||
|
|
|
@ -112,6 +112,10 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
after(:create) do |merge_request, evaluator|
|
||||
merge_request.cache_merge_request_closes_issues!
|
||||
end
|
||||
|
||||
factory :merged_merge_request, traits: [:merged]
|
||||
factory :closed_merge_request, traits: [:closed]
|
||||
factory :reopened_merge_request, traits: [:opened]
|
||||
|
|
|
@ -89,6 +89,7 @@ merge_requests:
|
|||
- merge_request_diff
|
||||
- events
|
||||
- merge_requests_closing_issues
|
||||
- cached_closes_issues
|
||||
- metrics
|
||||
- timelogs
|
||||
- head_pipeline
|
||||
|
|
|
@ -311,6 +311,51 @@ describe MergeRequest do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#visible_closing_issues_for' do
|
||||
let(:guest) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
let(:issue_1) { create(:issue, project: subject.source_project) }
|
||||
let(:issue_2) { create(:issue, project: subject.source_project) }
|
||||
let(:confidential_issue) { create(:issue, :confidential, project: subject.source_project) }
|
||||
|
||||
before do
|
||||
subject.project.add_developer(subject.author)
|
||||
subject.target_branch = subject.project.default_branch
|
||||
commit = double('commit1', safe_message: "Fixes #{issue_1.to_reference} #{issue_2.to_reference} #{confidential_issue.to_reference}")
|
||||
allow(subject).to receive(:commits).and_return([commit])
|
||||
end
|
||||
|
||||
it 'shows only allowed issues to guest' do
|
||||
subject.project.add_guest(guest)
|
||||
|
||||
subject.cache_merge_request_closes_issues!
|
||||
|
||||
expect(subject.visible_closing_issues_for(guest)).to match_array([issue_1, issue_2])
|
||||
end
|
||||
|
||||
it 'shows only allowed issues to developer' do
|
||||
subject.project.add_developer(developer)
|
||||
|
||||
subject.cache_merge_request_closes_issues!
|
||||
|
||||
expect(subject.visible_closing_issues_for(developer)).to match_array([issue_1, confidential_issue, issue_2])
|
||||
end
|
||||
|
||||
context 'when external issue tracker is enabled' do
|
||||
before do
|
||||
subject.project.has_external_issue_tracker = true
|
||||
subject.project.save!
|
||||
end
|
||||
|
||||
it 'calls non #closes_issues to retrieve data' do
|
||||
expect(subject).to receive(:closes_issues)
|
||||
expect(subject).not_to receive(:cached_closes_issues)
|
||||
|
||||
subject.visible_closing_issues_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cache_merge_request_closes_issues!' do
|
||||
before do
|
||||
subject.project.add_developer(subject.author)
|
||||
|
@ -325,6 +370,25 @@ describe MergeRequest do
|
|||
expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
|
||||
end
|
||||
|
||||
it 'does not cache closed issues when merge request is closed' do
|
||||
issue = create :issue, project: subject.project
|
||||
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
|
||||
|
||||
allow(subject).to receive(:commits).and_return([commit])
|
||||
allow(subject).to receive(:state).and_return("closed")
|
||||
|
||||
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
|
||||
end
|
||||
|
||||
it 'does not cache closed issues when merge request is merged' do
|
||||
issue = create :issue, project: subject.project
|
||||
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
|
||||
allow(subject).to receive(:commits).and_return([commit])
|
||||
allow(subject).to receive(:state).and_return("merged")
|
||||
|
||||
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
|
||||
end
|
||||
|
||||
context 'when both internal and external issue trackers are enabled' do
|
||||
before do
|
||||
subject.project.has_external_issue_tracker = true
|
||||
|
@ -633,6 +697,7 @@ describe MergeRequest do
|
|||
allow(subject).to receive(:commits).and_return([commit])
|
||||
allow(subject.project).to receive(:default_branch)
|
||||
.and_return(subject.target_branch)
|
||||
subject.cache_merge_request_closes_issues!
|
||||
|
||||
expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
|
||||
end
|
||||
|
@ -650,6 +715,8 @@ describe MergeRequest do
|
|||
end
|
||||
|
||||
it 'detects issues mentioned in description but not closed' do
|
||||
subject.cache_merge_request_closes_issues!
|
||||
|
||||
expect(subject.issues_mentioned_but_not_closing(subject.author).map(&:to_s)).to match_array(['TEST-2'])
|
||||
end
|
||||
end
|
||||
|
@ -780,9 +847,8 @@ describe MergeRequest do
|
|||
|
||||
subject.project.add_developer(subject.author)
|
||||
subject.description = "This issue Closes #{issue.to_reference}"
|
||||
|
||||
allow(subject.project).to receive(:default_branch)
|
||||
.and_return(subject.target_branch)
|
||||
allow(subject.project).to receive(:default_branch).and_return(subject.target_branch)
|
||||
subject.cache_merge_request_closes_issues!
|
||||
|
||||
expect(subject.merge_commit_message)
|
||||
.to match("Closes #{issue.to_reference}")
|
||||
|
|
|
@ -117,9 +117,9 @@ describe MergeRequestPresenter do
|
|||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
allow(resource.project).to receive(:default_branch)
|
||||
.and_return(resource.target_branch)
|
||||
resource.cache_merge_request_closes_issues!
|
||||
end
|
||||
|
||||
describe '#closing_issues_links' do
|
||||
|
|
|
@ -954,6 +954,7 @@ describe API::MergeRequests do
|
|||
issue = create(:issue, project: project)
|
||||
mr = merge_request.tap do |mr|
|
||||
mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
|
||||
mr.cache_merge_request_closes_issues!
|
||||
end
|
||||
|
||||
get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
|
||||
|
|
|
@ -20,7 +20,7 @@ describe Issues::ReopenService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when user is authrized to reopen issue' do
|
||||
context 'when user is authorized to reopen issue' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -49,6 +49,7 @@ describe MergeRequests::MergeService do
|
|||
issue = create :issue, project: project
|
||||
commit = double('commit', safe_message: "Fixes #{issue.to_reference}")
|
||||
allow(merge_request).to receive(:commits).and_return([commit])
|
||||
merge_request.cache_merge_request_closes_issues!
|
||||
|
||||
service.execute(merge_request)
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ describe MergeRequests::PostMergeService do
|
|||
allow(project).to receive(:default_branch).and_return('foo')
|
||||
|
||||
issue = create(:issue, project: project)
|
||||
allow(merge_request).to receive(:closes_issues).and_return([issue])
|
||||
allow(merge_request).to receive(:visible_closing_issues_for).and_return([issue])
|
||||
allow_any_instance_of(Issues::CloseService).to receive(:execute).with(issue, commit: merge_request).and_raise
|
||||
|
||||
expect { described_class.new(project, user, {}).execute(merge_request) }.to raise_error
|
||||
|
|
|
@ -47,6 +47,12 @@ describe MergeRequests::ReopenService do
|
|||
end
|
||||
end
|
||||
|
||||
it 'caches merge request closing issues' do
|
||||
expect(merge_request).to receive(:cache_merge_request_closes_issues!)
|
||||
|
||||
described_class.new(project, user, {}).execute(merge_request)
|
||||
end
|
||||
|
||||
it 'updates metrics' do
|
||||
metrics = merge_request.metrics
|
||||
service = double(MergeRequestMetricsService)
|
||||
|
|
Loading…
Reference in a new issue