Skip to content
Snippets Groups Projects
Commit fdb5f285 authored by Felipe Artur's avatar Felipe Artur
Browse files

Retrieve merge request closing issues from database cache

parent cb2e0730
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
Showing
with 112 additions and 12 deletions
Loading
Loading
@@ -52,6 +52,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
Loading
Loading
@@ -755,8 +757,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
Loading
Loading
@@ -769,6 +772,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
Loading
Loading
@@ -788,7 +803,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
Loading
Loading
@@ -836,7 +851,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
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
---
title: Retrieve merge request closing issues from database cache
merge_request: 20911
author:
type: fixed
Loading
Loading
@@ -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) }
Loading
Loading
Loading
Loading
@@ -100,6 +100,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]
Loading
Loading
Loading
Loading
@@ -87,6 +87,7 @@ merge_requests:
- merge_request_diff
- events
- merge_requests_closing_issues
- cached_closes_issues
- metrics
- timelogs
- head_pipeline
Loading
Loading
Loading
Loading
@@ -310,6 +310,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)
Loading
Loading
@@ -324,6 +369,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
Loading
Loading
@@ -632,6 +696,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
Loading
Loading
@@ -649,6 +714,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
Loading
Loading
@@ -779,9 +846,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}")
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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)
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment