Adds milestone search
Adds to search ILIKE search for milestones title in: - Milestones dashboard - Group milestones page - Project milestones page
This commit is contained in:
parent
e520a94641
commit
22eb2e4c22
21 changed files with 144 additions and 13 deletions
|
@ -27,7 +27,7 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
|
|||
def group_milestones
|
||||
groups = GroupsFinder.new(current_user, all_available: false).execute
|
||||
|
||||
DashboardGroupMilestone.build_collection(groups)
|
||||
DashboardGroupMilestone.build_collection(groups, params)
|
||||
end
|
||||
|
||||
# See [#39545](https://gitlab.com/gitlab-org/gitlab-ce/issues/39545) for info about the deprecation of dynamic milestones
|
||||
|
|
|
@ -115,6 +115,6 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:state).merge(group_ids: group.id)
|
||||
params.permit(:state, :search_title).merge(group_ids: group.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -147,6 +147,6 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
groups = project_group.self_and_ancestors.select(:id)
|
||||
end
|
||||
|
||||
params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
|
||||
params.permit(:state, :search_title).merge(project_ids: @project.id, group_ids: groups)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ class MilestonesFinder
|
|||
items = Milestone.all
|
||||
items = by_groups_and_projects(items)
|
||||
items = by_title(items)
|
||||
items = by_search_title(items)
|
||||
items = by_state(items)
|
||||
|
||||
order(items)
|
||||
|
@ -43,6 +44,14 @@ class MilestonesFinder
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def by_search_title(items)
|
||||
if params[:search_title].present?
|
||||
items.search_title(params[:search_title])
|
||||
else
|
||||
items
|
||||
end
|
||||
end
|
||||
|
||||
def by_state(items)
|
||||
Milestone.filter_by_state(items, params[:state])
|
||||
end
|
||||
|
|
|
@ -11,11 +11,12 @@ class DashboardGroupMilestone < GlobalMilestone
|
|||
@group_name = milestone.group.full_name
|
||||
end
|
||||
|
||||
def self.build_collection(groups)
|
||||
Milestone.of_groups(groups.select(:id))
|
||||
def self.build_collection(groups, params)
|
||||
milestones = Milestone.of_groups(groups.select(:id))
|
||||
.reorder_by_due_date_asc
|
||||
.order_by_name_asc
|
||||
.active
|
||||
.map { |m| new(m) }
|
||||
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
|
||||
milestones.map { |m| new(m) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,7 @@ class GlobalMilestone
|
|||
items = Milestone.of_projects(projects)
|
||||
.reorder_by_due_date_asc
|
||||
.order_by_name_asc
|
||||
items = items.search_title(params[:search_title]) if params[:search_title].present?
|
||||
|
||||
Milestone.filter_by_state(items, params[:state]).map { |m| new(m) }
|
||||
end
|
||||
|
|
|
@ -5,9 +5,10 @@ class GroupMilestone < GlobalMilestone
|
|||
|
||||
def self.build_collection(group, projects, params)
|
||||
params =
|
||||
{ state: params[:state] }
|
||||
{ state: params[:state], search_title: params[:search_title] }
|
||||
|
||||
project_milestones = Milestone.of_projects(projects)
|
||||
project_milestones = project_milestones.search_title(params[:search_title]) if params[:search_title].present?
|
||||
child_milestones = Milestone.filter_by_state(project_milestones, params[:state])
|
||||
grouped_milestones = child_milestones.group_by(&:title)
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ class Milestone < ActiveRecord::Base
|
|||
alias_attribute :name, :title
|
||||
|
||||
class << self
|
||||
# Searches for milestones matching the given query.
|
||||
# Searches for milestones with a matching title or description.
|
||||
#
|
||||
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
|
||||
#
|
||||
|
@ -88,6 +88,17 @@ class Milestone < ActiveRecord::Base
|
|||
fuzzy_search(query, [:title, :description])
|
||||
end
|
||||
|
||||
# Searches for milestones with a matching title.
|
||||
#
|
||||
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
|
||||
#
|
||||
# query - The search query as a String
|
||||
#
|
||||
# Returns an ActiveRecord::Relation.
|
||||
def search_title(query)
|
||||
fuzzy_search(query, [:title])
|
||||
end
|
||||
|
||||
def filter_by_state(milestones, state)
|
||||
case state
|
||||
when 'closed' then milestones.closed
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
.top-area
|
||||
= render 'shared/milestones_filter', counts: @milestone_states
|
||||
.nav-controls
|
||||
= render 'shared/milestones/search_form'
|
||||
|
||||
.milestones
|
||||
%ul.content-list
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
= render 'shared/milestones_filter', counts: @milestone_states
|
||||
|
||||
.nav-controls
|
||||
= render 'shared/milestones/search_form'
|
||||
= render 'shared/milestones_sort_dropdown'
|
||||
- if can?(current_user, :admin_milestone, @group)
|
||||
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-success"
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
|
||||
|
||||
.nav-controls
|
||||
= render 'shared/milestones/search_form'
|
||||
= render 'shared/milestones_sort_dropdown'
|
||||
- if can?(current_user, :admin_milestone, @project)
|
||||
= link_to new_project_milestone_path(@project), class: "btn btn-success qa-new-project-milestone", title: 'New milestone' do
|
||||
|
|
8
app/views/shared/milestones/_search_form.html.haml
Normal file
8
app/views/shared/milestones/_search_form.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
= form_tag request.path, method: :get do |f|
|
||||
= search_field_tag :search_title, params[:search_title],
|
||||
placeholder: _('Filter by milestone name'),
|
||||
class: 'form-control input-short',
|
||||
spellcheck: false
|
||||
= hidden_field_tag :state, params[:state]
|
||||
= hidden_field_tag :sort, params[:sort]
|
||||
|
5
changelogs/unreleased/54905-milestone-search.yml
Normal file
5
changelogs/unreleased/54905-milestone-search.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds milestone search
|
||||
merge_request: 24265
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: added
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
#
|
||||
# Example usage:
|
||||
#
|
||||
# union = Gitlab::SQL::Union.new(user.personal_projects, user.projects)
|
||||
# union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
|
||||
# sql = union.to_sql
|
||||
#
|
||||
# Project.where("id IN (#{sql})")
|
||||
|
|
|
@ -3163,6 +3163,9 @@ msgstr ""
|
|||
msgid "Filter by commit message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by milestone name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by two-factor authentication"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -56,6 +56,24 @@ describe Dashboard::MilestonesController do
|
|||
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
|
||||
end
|
||||
|
||||
it 'searches legacy project milestones by title when search_title is given' do
|
||||
project_milestone = create(:milestone, title: 'Project milestone title', project: project)
|
||||
|
||||
get :index, params: { search_title: 'Project mil' }
|
||||
|
||||
expect(response.body).to include(project_milestone.title)
|
||||
expect(response.body).not_to include(group_milestone.title)
|
||||
end
|
||||
|
||||
it 'searches group milestones by title when search_title is given' do
|
||||
group_milestone = create(:milestone, title: 'Group milestone title', group: group)
|
||||
|
||||
get :index, params: { search_title: 'Group mil' }
|
||||
|
||||
expect(response.body).to include(group_milestone.title)
|
||||
expect(response.body).not_to include(project_milestone.title)
|
||||
end
|
||||
|
||||
it 'should contain group and project milestones to which the user belongs to' do
|
||||
get :index
|
||||
|
||||
|
|
|
@ -32,10 +32,35 @@ describe Groups::MilestonesController do
|
|||
end
|
||||
|
||||
describe '#index' do
|
||||
it 'shows group milestones page' do
|
||||
get :index, params: { group_id: group.to_param }
|
||||
describe 'as HTML' do
|
||||
render_views
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
it 'shows group milestones page' do
|
||||
milestone
|
||||
|
||||
get :index, params: { group_id: group.to_param }
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response.body).to include(milestone.title)
|
||||
end
|
||||
|
||||
it 'searches legacy milestones by title when search_title is given' do
|
||||
project_milestone = create(:milestone, project: project, title: 'Project milestone title')
|
||||
|
||||
get :index, params: { group_id: group.to_param, search_title: 'Project mil' }
|
||||
|
||||
expect(response.body).to include(project_milestone.title)
|
||||
expect(response.body).not_to include(milestone.title)
|
||||
end
|
||||
|
||||
it 'searches group milestones by title when search_title is given' do
|
||||
group_milestone = create(:milestone, title: 'Group milestone title', group: group)
|
||||
|
||||
get :index, params: { group_id: group.to_param, search_title: 'Group mil' }
|
||||
|
||||
expect(response.body).to include(group_milestone.title)
|
||||
expect(response.body).not_to include(milestone.title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as JSON' do
|
||||
|
|
|
@ -42,10 +42,11 @@ describe Projects::MilestonesController do
|
|||
|
||||
describe "#index" do
|
||||
context "as html" do
|
||||
def render_index(project:, page:)
|
||||
def render_index(project:, page:, search_title: '')
|
||||
get :index, params: {
|
||||
namespace_id: project.namespace.id,
|
||||
project_id: project.id,
|
||||
search_title: search_title,
|
||||
page: page
|
||||
}
|
||||
end
|
||||
|
@ -59,6 +60,15 @@ describe Projects::MilestonesController do
|
|||
expect(milestones.where(project_id: nil)).to be_empty
|
||||
end
|
||||
|
||||
it 'searches milestones by title when search_title is given' do
|
||||
milestone1 = create(:milestone, title: 'Project milestone title', project: project)
|
||||
|
||||
render_index project: project, page: 1, search_title: 'Project mile'
|
||||
|
||||
milestones = assigns(:milestones)
|
||||
expect(milestones).to eq([milestone1])
|
||||
end
|
||||
|
||||
it 'renders paginated milestones without missing or duplicates' do
|
||||
allow(Milestone).to receive(:default_per_page).and_return(2)
|
||||
create_list(:milestone, 5, project: project)
|
||||
|
|
|
@ -69,6 +69,12 @@ describe MilestonesFinder do
|
|||
|
||||
expect(result.to_a).to contain_exactly(milestone_1)
|
||||
end
|
||||
|
||||
it 'filters by search_title' do
|
||||
result = described_class.new(params.merge(search_title: 'one t')).execute
|
||||
|
||||
expect(result.to_a).to contain_exactly(milestone_1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_by' do
|
||||
|
|
|
@ -91,6 +91,12 @@ describe GlobalMilestone do
|
|||
it 'sorts collection by due date' do
|
||||
expect(global_milestones.map(&:due_date)).to eq [milestone1_due_date, milestone1_due_date, milestone1_due_date, nil, nil, nil]
|
||||
end
|
||||
|
||||
it 'filters milestones by search_title when params[:search_title] is present' do
|
||||
global_milestones = described_class.build_collection(projects, { search_title: 'v1.2' })
|
||||
|
||||
expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding new milestones' do
|
||||
|
|
|
@ -240,6 +240,29 @@ describe Milestone do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#search_title' do
|
||||
let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
|
||||
|
||||
it 'returns milestones with a matching title' do
|
||||
expect(described_class.search_title(milestone.title)) .to eq([milestone])
|
||||
end
|
||||
|
||||
it 'returns milestones with a partially matching title' do
|
||||
expect(described_class.search_title(milestone.title[0..2])).to eq([milestone])
|
||||
end
|
||||
|
||||
it 'returns milestones with a matching title regardless of the casing' do
|
||||
expect(described_class.search_title(milestone.title.upcase))
|
||||
.to eq([milestone])
|
||||
end
|
||||
|
||||
it 'searches only on the title and ignores milestones with a matching description' do
|
||||
create(:milestone, title: 'bar', description: 'foo')
|
||||
|
||||
expect(described_class.search_title(milestone.title)) .to eq([milestone])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#for_projects_and_groups' do
|
||||
let(:project) { create(:project) }
|
||||
let(:project_other) { create(:project) }
|
||||
|
|
Loading…
Reference in a new issue