Commit 12e01dae authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Add group milestones in upcoming filter

parent c07bf1ab
...@@ -149,6 +149,18 @@ class IssuableFinder ...@@ -149,6 +149,18 @@ class IssuableFinder
end end
end end
def related_groups
if project? && project && project.group && Ability.allowed?(current_user, :read_group, project.group)
project.group.self_and_ancestors
elsif group
[group]
elsif current_user
Gitlab::GroupHierarchy.new(current_user.authorized_groups, current_user.groups).all_groups
else
[]
end
end
def project? def project?
params[:project_id].present? params[:project_id].present?
end end
...@@ -163,8 +175,10 @@ class IssuableFinder ...@@ -163,8 +175,10 @@ class IssuableFinder
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def projects(items = nil) def projects
return @projects = project if project? return @projects if defined?(@projects)
return @projects = [project] if project?
projects = projects =
if current_user && params[:authorized_only].presence && !current_user_related? if current_user && params[:authorized_only].presence && !current_user_related?
...@@ -459,7 +473,7 @@ class IssuableFinder ...@@ -459,7 +473,7 @@ class IssuableFinder
elsif filter_by_any_milestone? elsif filter_by_any_milestone?
items = items.any_milestone items = items.any_milestone
elsif filter_by_upcoming_milestone? elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) upcoming_ids = Milestone.upcoming_ids(projects, related_groups)
items = items.left_joins_milestones.where(milestone_id: upcoming_ids) items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
elsif filter_by_started_milestone? elsif filter_by_started_milestone?
items = items.left_joins_milestones.where('milestones.start_date <= NOW()') items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
......
...@@ -40,6 +40,7 @@ class Milestone < ActiveRecord::Base ...@@ -40,6 +40,7 @@ class Milestone < ActiveRecord::Base
scope :for_projects_and_groups, -> (project_ids, group_ids) do scope :for_projects_and_groups, -> (project_ids, group_ids) do
conditions = [] conditions = []
conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any? conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any?
conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any? conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any?
...@@ -129,18 +130,29 @@ class Milestone < ActiveRecord::Base ...@@ -129,18 +130,29 @@ class Milestone < ActiveRecord::Base
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/) @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end end
def self.upcoming_ids_by_projects(projects) def self.upcoming_ids(projects, groups)
rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now) rel = unscoped
.for_projects_and_groups(projects&.map(&:id), groups&.map(&:id))
.active.where('milestones.due_date > NOW()')
if Gitlab::Database.postgresql? if Gitlab::Database.postgresql?
rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id') rel.order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
else else
# We need to use MySQL's NULL-safe comparison operator `<=>` here
# because one of `project_id` or `group_id` is always NULL
join_clause = <<~HEREDOC
LEFT OUTER JOIN milestones earlier_milestones
ON milestones.project_id <=> earlier_milestones.project_id
AND milestones.group_id <=> earlier_milestones.group_id
AND milestones.due_date > earlier_milestones.due_date
AND earlier_milestones.due_date > NOW()
AND earlier_milestones.state = 'active'
HEREDOC
rel rel
.group(:project_id, :due_date, :id) .joins(join_clause)
.having('due_date = MIN(due_date)') .where('earlier_milestones.id IS NULL')
.pluck(:id, :project_id, :due_date) .select(:id)
.uniq(&:second)
.map(&:first)
end end
end end
......
...@@ -174,9 +174,13 @@ describe IssuesFinder do ...@@ -174,9 +174,13 @@ describe IssuesFinder do
context 'filtering by upcoming milestone' do context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } } let(:params) { { milestone_title: Milestone::Upcoming.name } }
let!(:group) { create(:group, :public) }
let!(:group_member) { create(:group_member, group: group, user: user) }
let(:project_no_upcoming_milestones) { create(:project, :public) } let(:project_no_upcoming_milestones) { create(:project, :public) }
let(:project_next_1_1) { create(:project, :public) } let(:project_next_1_1) { create(:project, :public) }
let(:project_next_8_8) { create(:project, :public) } let(:project_next_8_8) { create(:project, :public) }
let(:project_in_group) { create(:project, :public, namespace: group) }
let(:yesterday) { Date.today - 1.day } let(:yesterday) { Date.today - 1.day }
let(:tomorrow) { Date.today + 1.day } let(:tomorrow) { Date.today + 1.day }
...@@ -187,21 +191,22 @@ describe IssuesFinder do ...@@ -187,21 +191,22 @@ describe IssuesFinder do
[ [
create(:milestone, :closed, project: project_no_upcoming_milestones), create(:milestone, :closed, project: project_no_upcoming_milestones),
create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now), create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now), create(:milestone, project: project_next_1_1, title: '8.9', due_date: ten_days_from_now),
create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday), create(:milestone, project: project_next_8_8, title: '1.2', due_date: yesterday),
create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow) create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow),
create(:milestone, group: group, title: '9.9', due_date: tomorrow)
] ]
end end
before do before do
milestones.each do |milestone| milestones.each do |milestone|
create(:issue, project: milestone.project, milestone: milestone, author: user, assignees: [user]) create(:issue, project: milestone.project || project_in_group, milestone: milestone, author: user, assignees: [user])
end end
end end
it 'returns issues in the upcoming milestone for each project' do it 'returns issues in the upcoming milestone for each project or group' do
expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8') expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8', '9.9')
expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now) expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now, tomorrow)
end end
end end
......
...@@ -240,7 +240,22 @@ describe Milestone do ...@@ -240,7 +240,22 @@ describe Milestone do
end end
end end
describe '.upcoming_ids_by_projects' do describe '.upcoming_ids' do
let(:group_1) { create(:group) }
let(:group_2) { create(:group) }
let(:group_3) { create(:group) }
let(:groups) { [group_1, group_2, group_3] }
let!(:past_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now - 1.day) }
let!(:current_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 1.day) }
let!(:future_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 2.days) }
let!(:past_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now - 1.day) }
let!(:closed_milestone_group_2) { create(:milestone, :closed, group: group_2, due_date: Time.now + 1.day) }
let!(:current_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now + 2.days) }
let!(:past_milestone_group_3) { create(:milestone, group: group_3, due_date: Time.now - 1.day) }
let(:project_1) { create(:project) } let(:project_1) { create(:project) }
let(:project_2) { create(:project) } let(:project_2) { create(:project) }
let(:project_3) { create(:project) } let(:project_3) { create(:project) }
...@@ -258,14 +273,20 @@ describe Milestone do ...@@ -258,14 +273,20 @@ describe Milestone do
# The call to `#try` is because this returns a relation with a Postgres DB, # The call to `#try` is because this returns a relation with a Postgres DB,
# and an array of IDs with a MySQL DB. # and an array of IDs with a MySQL DB.
let(:milestone_ids) { described_class.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } } let(:milestone_ids) { described_class.upcoming_ids(projects, groups).map { |id| id.try(:id) || id } }
it 'returns the next upcoming open milestone ID for each project' do it 'returns the next upcoming open milestone ID for each project and group' do
expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id) expect(milestone_ids).to contain_exactly(
current_milestone_project_1.id,
current_milestone_project_2.id,
current_milestone_group_1.id,
current_milestone_group_2.id
)
end end
context 'when the projects have no open upcoming milestones' do context 'when the projects and groups have no open upcoming milestones' do
let(:projects) { [project_3] } let(:projects) { [project_3] }
let(:groups) { [group_3] }
it 'returns no results' do it 'returns no results' do
expect(milestone_ids).to be_empty expect(milestone_ids).to be_empty
......
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