Merge pull request #4981 from dmacjam/fix_limited_search_results

FIX: limited search results
This commit is contained in:
Neil Lalonde 2017-07-31 20:23:57 -04:00 committed by GitHub
commit fa3c240e8b
8 changed files with 148 additions and 55 deletions

View File

@ -14,12 +14,13 @@ const SortOrders = [
{name: I18n.t('search.latest_topic'), id: 4, term: 'order:latest_topic'}, {name: I18n.t('search.latest_topic'), id: 4, term: 'order:latest_topic'},
]; ];
const PAGE_LIMIT = 10;
export default Ember.Controller.extend({ export default Ember.Controller.extend({
application: Ember.inject.controller(), application: Ember.inject.controller(),
bulkSelectEnabled: null, bulkSelectEnabled: null,
loading: Em.computed.not("model"), loading: false,
queryParams: ["q", "expanded", "context_id", "context", "skip_context"], queryParams: ["q", "expanded", "context_id", "context", "skip_context"],
q: null, q: null,
selected: [], selected: [],
@ -30,11 +31,8 @@ export default Ember.Controller.extend({
sortOrder: 0, sortOrder: 0,
sortOrders: SortOrders, sortOrders: SortOrders,
invalidSearch: false, invalidSearch: false,
page: 1,
@computed('model.posts') resultCount: null,
resultCount(posts) {
return posts && posts.length;
},
@computed('resultCount') @computed('resultCount')
hasResults(resultCount) { hasResults(resultCount) {
@ -113,6 +111,7 @@ export default Ember.Controller.extend({
@observes('sortOrder') @observes('sortOrder')
triggerSearch() { triggerSearch() {
if (this._searchOnSortChange) { if (this._searchOnSortChange) {
this.set("page", 1);
this._search(); this._search();
} }
}, },
@ -143,6 +142,11 @@ export default Ember.Controller.extend({
this.set("application.showFooter", !this.get("loading")); this.set("application.showFooter", !this.get("loading"));
}, },
@observes('model.posts.length')
resultCountChanged() {
this.set("resultCount", this.get("model.posts.length"));
},
@computed('hasResults') @computed('hasResults')
canBulkSelect(hasResults) { canBulkSelect(hasResults) {
return this.currentUser && this.currentUser.staff && hasResults; return this.currentUser && this.currentUser.staff && hasResults;
@ -158,6 +162,11 @@ export default Ember.Controller.extend({
return iconHTML(expanded ? "caret-down" : "caret-right"); return iconHTML(expanded ? "caret-down" : "caret-right");
}, },
@computed('page')
isLastPage(page) {
return page === PAGE_LIMIT;
},
_search() { _search() {
if (this.get("searching")) { return; } if (this.get("searching")) { return; }
@ -169,10 +178,11 @@ export default Ember.Controller.extend({
} }
this.set("searching", true); this.set("searching", true);
this.set("loading", true);
this.set('bulkSelectEnabled', false); this.set('bulkSelectEnabled', false);
this.get('selected').clear(); this.get('selected').clear();
var args = { q: searchTerm }; var args = { q: searchTerm, page: this.get('page') };
const sortOrder = this.get("sortOrder"); const sortOrder = this.get("sortOrder");
if (sortOrder && SortOrders[sortOrder].term) { if (sortOrder && SortOrders[sortOrder].term) {
@ -180,7 +190,6 @@ export default Ember.Controller.extend({
} }
this.set("q", args.q); this.set("q", args.q);
this.set("model", null);
const skip = this.get("skip_context"); const skip = this.get("skip_context");
if ((!skip && this.get('context')) || skip==="false"){ if ((!skip && this.get('context')) || skip==="false"){
@ -199,9 +208,20 @@ export default Ember.Controller.extend({
this.set('q', results.grouped_search_result.term); this.set('q', results.grouped_search_result.term);
} }
setTransient('lastSearch', { searchKey, model }, 5); if(args.page > 1){
this.set("model", model); if (model){
}).finally(() => this.set("searching", false)); this.get("model").posts.pushObjects(model.posts);
this.get("model").topics.pushObjects(model.topics);
this.get("model").set('grouped_search_result', results.grouped_search_result);
}
}else{
setTransient('lastSearch', { searchKey, model }, 5);
this.set("model", model);
}
}).finally(() => {
this.set("searching", false);
this.set("loading", false);
});
}, },
actions: { actions: {
@ -227,11 +247,20 @@ export default Ember.Controller.extend({
}, },
search() { search() {
this.set("page", 1);
this._search(); this._search();
}, },
toggleAdvancedSearch() { toggleAdvancedSearch() {
this.toggleProperty('expanded'); this.toggleProperty('expanded');
} },
loadMore() {
var page = this.get('page');
if (this.get('model.grouped_search_result.more_full_page_results') && !this.get("loading") && page < PAGE_LIMIT){
this.incrementProperty("page");
this._search();
}
},
} }
}); });

View File

@ -45,32 +45,23 @@
</div> </div>
{{/if}} {{/if}}
{{#conditional-loading-spinner condition=loading}} {{#if hasResults}}
<div class='search-title clearfix'>
{{#unless hasResults}} <div class='result-count'>
<h3> <span>
{{#if searchActive}} {{{i18n "search.result_count" count=resultCount term=noSortQ}}}
{{i18n "search.no_results"}} </span>
{{/if}}
</h3>
{{/unless}}
{{#if hasResults}}
<div class='search-title clearfix'>
<div class='result-count'>
<span>
{{{i18n "search.result_count" count=resultCount term=noSortQ}}}
</span>
</div>
<div class='sort-by'>
<span class='desc'>
{{i18n "search.sort_by"}}
</span>
{{combo-box value=sortOrder content=sortOrders castInteger="true"}}
</div>
</div> </div>
{{/if}} <div class='sort-by'>
<span class='desc'>
{{i18n "search.sort_by"}}
</span>
{{combo-box value=sortOrder content=sortOrders castInteger="true"}}
</div>
</div>
{{/if}}
{{#load-more selector=".fps-result" action="loadMore"}}
{{#each model.posts as |result|}} {{#each model.posts as |result|}}
<div class='fps-result'> <div class='fps-result'>
<div class='author'> <div class='author'>
@ -124,11 +115,30 @@
</div> </div>
{{/each}} {{/each}}
{{#if hasResults}} {{#conditional-loading-spinner condition=loading }}
<h3 class="search-footer"> {{#unless hasResults}}
{{i18n "search.no_more_results"}} <h3>
</h3> {{#if searchActive}}
{{/if}} {{i18n "search.no_results"}}
{{/if}}
</h3>
{{/unless}}
{{#if hasResults}}
{{#unless loading}}
<h3 class="search-footer">
{{#if model.grouped_search_result.more_full_page_results}}
{{#if isLastPage }}
{{i18n "search.more_results"}}
{{/if}}
{{else}}
{{i18n "search.no_more_results"}}
{{/if}}
</h3>
{{/unless}}
{{/if}}
{{/conditional-loading-spinner}}
{{/load-more}}
{{/conditional-loading-spinner}}
{{/d-section}} {{/d-section}}

View File

@ -13,7 +13,10 @@ class SearchController < ApplicationController
type_filter: 'topic', type_filter: 'topic',
guardian: guardian, guardian: guardian,
include_blurbs: true, include_blurbs: true,
blurb_length: 300 blurb_length: 300,
page: if params[:page].to_i <= 10
[params[:page].to_i, 1].max
end
} }
context, type = lookup_search_context context, type = lookup_search_context

View File

@ -2,7 +2,7 @@ class GroupedSearchResultSerializer < ApplicationSerializer
has_many :posts, serializer: SearchPostSerializer has_many :posts, serializer: SearchPostSerializer
has_many :users, serializer: SearchResultUserSerializer has_many :users, serializer: SearchResultUserSerializer
has_many :categories, serializer: BasicCategorySerializer has_many :categories, serializer: BasicCategorySerializer
attributes :more_posts, :more_users, :more_categories, :term, :search_log_id attributes :more_posts, :more_users, :more_categories, :term, :search_log_id, :more_full_page_results
def search_log_id def search_log_id
object.search_log_id object.search_log_id

View File

@ -1341,6 +1341,7 @@ en:
searching: "Searching ..." searching: "Searching ..."
post_format: "#{{post_number}} by {{username}}" post_format: "#{{post_number}} by {{username}}"
results_page: "Search Results" results_page: "Search Results"
more_results: "There are more results. Please narrow your search criteria."
context: context:
user: "Search posts by @{{username}}" user: "Search posts by @{{username}}"

View File

@ -155,8 +155,8 @@ class Search
@search_context = @opts[:search_context] @search_context = @opts[:search_context]
@include_blurbs = @opts[:include_blurbs] || false @include_blurbs = @opts[:include_blurbs] || false
@blurb_length = @opts[:blurb_length] @blurb_length = @opts[:blurb_length]
@limit = Search.per_facet
@valid = true @valid = true
@page = @opts[:page]
# Removes any zero-width characters from search terms # Removes any zero-width characters from search terms
term.to_s.gsub!(/[\u200B-\u200D\uFEFF]/, '') term.to_s.gsub!(/[\u200B-\u200D\uFEFF]/, '')
@ -174,10 +174,6 @@ class Search
@search_context = @guardian.user @search_context = @guardian.user
end end
if @opts[:type_filter].present?
@limit = Search.per_filter
end
@results = GroupedSearchResults.new( @results = GroupedSearchResults.new(
@opts[:type_filter], @opts[:type_filter],
clean_term, clean_term,
@ -187,6 +183,22 @@ class Search
) )
end end
def limit
if @opts[:type_filter].present?
Search.per_filter + 1
else
Search.per_facet + 1
end
end
def offset
if @page && @opts[:type_filter].present?
(@page - 1) * Search.per_filter
else
0
end
end
def valid? def valid?
@valid @valid
end end
@ -557,7 +569,6 @@ class Search
raise Discourse::InvalidAccess.new("invalid type filter") unless Search.facets.include?(@results.type_filter) raise Discourse::InvalidAccess.new("invalid type filter") unless Search.facets.include?(@results.type_filter)
send("#{@results.type_filter}_search") send("#{@results.type_filter}_search")
else else
@limit = Search.per_facet + 1
unless @search_context unless @search_context
user_search if @term.present? user_search if @term.present?
category_search if @term.present? category_search if @term.present?
@ -753,6 +764,7 @@ class Search
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories) posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories)
end end
posts = posts.offset(offset)
posts.limit(limit) posts.limit(limit)
end end
@ -795,10 +807,10 @@ class Search
query = query =
if @order == :likes if @order == :likes
# likes are a pain to aggregate so skip # likes are a pain to aggregate so skip
posts_query(@limit, private_messages: opts[:private_messages]) posts_query(limit, private_messages: opts[:private_messages])
.select('topics.id', "posts.post_number") .select('topics.id', "posts.post_number")
else else
posts_query(@limit, aggregate_search: true, private_messages: opts[:private_messages]) posts_query(limit, aggregate_search: true, private_messages: opts[:private_messages])
.select('topics.id', "#{min_or_max}(posts.post_number) post_number") .select('topics.id', "#{min_or_max}(posts.post_number) post_number")
.group('topics.id') .group('topics.id')
end end
@ -846,7 +858,7 @@ class Search
def topic_search def topic_search
if @search_context.is_a?(Topic) if @search_context.is_a?(Topic)
posts = posts_eager_loads(posts_query(@limit)) posts = posts_eager_loads(posts_query(limit))
.where('posts.topic_id = ?', @search_context.id) .where('posts.topic_id = ?', @search_context.id)
posts.each do |post| posts.each do |post|

View File

@ -19,7 +19,8 @@ class Search
:more_users, :more_users,
:term, :term,
:search_context, :search_context,
:include_blurbs :include_blurbs,
:more_full_page_results
) )
attr_accessor :search_log_id attr_accessor :search_log_id
@ -50,7 +51,9 @@ class Search
def add(object) def add(object)
type = object.class.to_s.downcase.pluralize type = object.class.to_s.downcase.pluralize
if !@type_filter.present? && send(type).length == Search.per_facet if @type_filter.present? && send(type).length == Search.per_filter
@more_full_page_results = true
elsif !@type_filter.present? && send(type).length == Search.per_facet
instance_variable_set("@more_#{type}".to_sym, true) instance_variable_set("@more_#{type}".to_sym, true)
else else
(send type) << object (send type) << object

View File

@ -843,4 +843,39 @@ describe Search do
end end
end end
context 'pagination' do
let(:number_of_results) { 2 }
let!(:post1) { Fabricate(:post, raw: 'hello hello hello hello hello')}
let!(:post2) { Fabricate(:post, raw: 'hello hello hello hello')}
let!(:post3) { Fabricate(:post, raw: 'hello hello hello')}
let!(:post4) { Fabricate(:post, raw: 'hello hello')}
let!(:post5) { Fabricate(:post, raw: 'hello')}
before do
Search.stubs(:per_filter).returns(number_of_results)
end
it 'returns more results flag' do
results = Search.execute('hello', type_filter: 'topic')
results2 = Search.execute('hello', type_filter: 'topic', page: 2)
expect(results.posts.length).to eq(number_of_results)
expect(results.posts.map(&:id)).to eq([post1.id, post2.id])
expect(results.more_full_page_results).to eq(true)
expect(results2.posts.length).to eq(number_of_results)
expect(results2.posts.map(&:id)).to eq([post3.id, post4.id])
expect(results2.more_full_page_results).to eq(true)
end
it 'correctly search with page parameter' do
search = Search.new('hello', type_filter: 'topic', page: 3)
results = search.execute
expect(search.offset).to eq(2 * number_of_results)
expect(results.posts.length).to eq(1)
expect(results.posts).to eq([post5])
expect(results.more_full_page_results).to eq(nil)
end
end
end end