Commit 15e02add authored by Felipe Artur's avatar Felipe Artur

Add blocked by issues count on issues metadata

Backend part of showing blocked by issues count on issues list.
parent 1fbf76cc
......@@ -2,7 +2,7 @@
- issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- 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
%li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') }
......
......@@ -65,6 +65,23 @@ class IssueLink < ApplicationRecord
.joins("INNER JOIN issues ON issues.id = issue_links.#{blocking_key}")
.where('issues.state_id' => Issuable::STATE_ID_MAP[:opened])
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
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
expect(described_class.inverse_link_type('is_blocked_by')).to eq 'blocks'
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
......@@ -11,7 +11,12 @@ module API
# Avoids an N+1 query when metadata is included
def issuable_metadata(subject, options, method, args = nil)
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
......
......@@ -7,11 +7,13 @@ module Gitlab
# data structure to store issuable meta data like
# upvotes, downvotes, notes and closing merge requests counts for issues and merge requests
# this avoiding n+1 queries when loading issuable collections on frontend
IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :mrs_count) do
def merge_requests_count(user = nil)
mrs_count
end
end
IssuableMeta = Struct.new(
:upvotes,
:downvotes,
:user_notes_count,
:merge_requests_count,
:blocking_issues_count # EE-ONLY
)
attr_reader :current_user, :issuable_collection
......@@ -95,3 +97,5 @@ module Gitlab
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