Commit 5cbcc865 authored by charlie ablett's avatar charlie ablett

Merge branch 'issue_217569' into 'master'

Add blocking issues count on issues metadata

See merge request gitlab-org/gitlab!32340
parents bf6b7caf 15e02add
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- issue_votes = @issuable_meta_data[issuable.id] - issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_path = issuable_path(issuable, anchor: 'notes') - issuable_path = issuable_path(issuable, anchor: 'notes')
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count(current_user) - issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0 - if issuable_mr > 0
%li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') } %li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') }
......
...@@ -65,6 +65,23 @@ class IssueLink < ApplicationRecord ...@@ -65,6 +65,23 @@ class IssueLink < ApplicationRecord
.joins("INNER JOIN issues ON issues.id = issue_links.#{blocking_key}") .joins("INNER JOIN issues ON issues.id = issue_links.#{blocking_key}")
.where('issues.state_id' => Issuable::STATE_ID_MAP[:opened]) .where('issues.state_id' => Issuable::STATE_ID_MAP[:opened])
end end
def blocking_issues_for_collection(issues_ids)
from_union([
select('COUNT(*), issue_links.source_id AS blocking_issue_id')
.joins(:target)
.where(issues: { state_id: Issue.available_states[:opened] })
.where(link_type: TYPE_BLOCKS)
.where(source_id: issues_ids)
.group(:blocking_issue_id),
select('COUNT(*), issue_links.target_id AS blocking_issue_id')
.joins(:source)
.where(issues: { state_id: Issue.available_states[:opened] })
.where(link_type: TYPE_IS_BLOCKED_BY)
.where(target_id: issues_ids)
.group(:blocking_issue_id)
], remove_duplicates: false).select('blocking_issue_id, SUM(count) AS count').group('blocking_issue_id')
end
end end
def check_self_relation def check_self_relation
......
# frozen_string_literal: true
module EE
module Gitlab
module IssuableMetadata
extend ::Gitlab::Utils::Override
override :metadata_for_issuable
def metadata_for_issuable(id)
return super unless ::Feature.enabled?(:blocking_issues_counts)
super.tap do |data|
blocking_count =
grouped_blocking_issues_count.find do |issue_link|
issue_link.blocking_issue_id == id
end
data.blocking_issues_count = blocking_count.try(:count).to_i
end
end
def grouped_blocking_issues_count
strong_memoize(:grouped_blocking_issues_count) do
next IssueLink.none unless collection_type == 'Issue'
IssueLink.blocking_issues_for_collection(issuable_ids)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::IssuableMetadata do
let_it_be(:user) { create(:user) }
let_it_be(:project1) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
let_it_be(:project2) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
context 'issues' do
# blocked issues
let_it_be(:blocked_issue_1) { create(:issue, author: user, project: project1) }
let_it_be(:blocked_issue_2) { create(:issue, author: user, project: project2) }
let_it_be(:blocked_issue_3) { create(:issue, author: user, project: project1) }
let_it_be(:closed_blocked_issue) { create(:issue, author: user, project: project2, state: :closed) }
# blocking issues (as target or source)
let_it_be(:blocking_issue_1) { create(:issue, project: project1) }
let_it_be(:blocking_issue_2) { create(:issue, project: project2) }
before_all do
create(:issue_link, source: blocking_issue_1, target: blocked_issue_1, link_type: IssueLink::TYPE_BLOCKS)
create(:issue_link, source: blocking_issue_2, target: blocked_issue_2, link_type: IssueLink::TYPE_BLOCKS)
create(:issue_link, source: blocking_issue_1, target: closed_blocked_issue, link_type: IssueLink::TYPE_BLOCKS)
create(:issue_link, source: blocked_issue_3, target: blocking_issue_1, link_type: IssueLink::TYPE_IS_BLOCKED_BY)
end
it 'aggregates stats on issues' do
data = described_class.new(user, Issue.all.limit(6)).data # rubocop: disable CodeReuse/ActiveRecord
expect(data.count).to eq(6)
expect(data[blocking_issue_1.id].blocking_issues_count).to eq(2)
expect(data[blocking_issue_2.id].blocking_issues_count).to eq(1)
expect(data[blocked_issue_1.id].blocking_issues_count).to eq(0)
end
context 'when blocking_issues_counts feature flag is disabled' do
before do
stub_feature_flags(blocking_issues_counts: false)
end
it 'does not return blocking_issues_counts' do
create(:award_emoji, :upvote, awardable: blocking_issue_1)
meta_data = described_class.new(user, Issue.all.limit(7)).data # rubocop: disable CodeReuse/ActiveRecord
expect(meta_data.values.map { |value| value.blocking_issues_count }.uniq).to eq([nil])
# Make sure other properties are still being fetched
expect(meta_data[blocking_issue_1.id].upvotes).to eq(1)
end
end
end
end
...@@ -84,4 +84,22 @@ RSpec.describe IssueLink do ...@@ -84,4 +84,22 @@ RSpec.describe IssueLink do
expect(described_class.inverse_link_type('is_blocked_by')).to eq 'blocks' expect(described_class.inverse_link_type('is_blocked_by')).to eq 'blocks'
end end
end end
describe '.blocking_issues_for_collection' do
it 'returns blocking issues count grouped by issue id' do
issue_1 = create(:issue)
issue_2 = create(:issue)
issue_3 = create(:issue)
blocking_issue_1 = create(:issue, project: issue_1.project)
blocking_issue_2 = create(:issue, project: issue_2.project)
create(:issue_link, source: blocking_issue_1, target: issue_1, link_type: IssueLink::TYPE_BLOCKS)
create(:issue_link, source: issue_2, target: blocking_issue_1, link_type: IssueLink::TYPE_IS_BLOCKED_BY)
create(:issue_link, source: blocking_issue_2, target: issue_3, link_type: IssueLink::TYPE_BLOCKS)
results = described_class.blocking_issues_for_collection([blocking_issue_1, blocking_issue_2])
expect(results.find { |link| link.blocking_issue_id == blocking_issue_1.id }.count).to eq(2)
expect(results.find { |link| link.blocking_issue_id == blocking_issue_2.id }.count).to eq(1)
end
end
end end
...@@ -11,7 +11,12 @@ module API ...@@ -11,7 +11,12 @@ module API
# Avoids an N+1 query when metadata is included # Avoids an N+1 query when metadata is included
def issuable_metadata(subject, options, method, args = nil) def issuable_metadata(subject, options, method, args = nil)
cached_subject = options.dig(:issuable_metadata, subject.id) cached_subject = options.dig(:issuable_metadata, subject.id)
(cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
if cached_subject
cached_subject[method]
else
subject.public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
end
end end
end end
end end
......
...@@ -7,11 +7,13 @@ module Gitlab ...@@ -7,11 +7,13 @@ module Gitlab
# data structure to store issuable meta data like # data structure to store issuable meta data like
# upvotes, downvotes, notes and closing merge requests counts for issues and merge requests # upvotes, downvotes, notes and closing merge requests counts for issues and merge requests
# this avoiding n+1 queries when loading issuable collections on frontend # this avoiding n+1 queries when loading issuable collections on frontend
IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :mrs_count) do IssuableMeta = Struct.new(
def merge_requests_count(user = nil) :upvotes,
mrs_count :downvotes,
end :user_notes_count,
end :merge_requests_count,
:blocking_issues_count # EE-ONLY
)
attr_reader :current_user, :issuable_collection attr_reader :current_user, :issuable_collection
...@@ -95,3 +97,5 @@ module Gitlab ...@@ -95,3 +97,5 @@ module Gitlab
end end
end end
end end
Gitlab::IssuableMetadata.prepend_if_ee('EE::Gitlab::IssuableMetadata')
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