FEATURE: implement before and after filters in search remove max_age and min_age

supports

- before:monday
- after:june
- before:2001
- before:2001-01-22
This commit is contained in:
Sam Saffron 2016-03-14 23:27:02 +11:00
parent b35186bfe2
commit e00850a1ab
3 changed files with 89 additions and 14 deletions

View File

@ -2723,7 +2723,7 @@ en:
<tr><td><code>category:foo</code></td><td><code>user:foo</code></td><td><code>group:foo</code></td><td><code>badge:foo</code></td><td></td></tr> <tr><td><code>category:foo</code></td><td><code>user:foo</code></td><td><code>group:foo</code></td><td><code>badge:foo</code></td><td></td></tr>
<tr><td><code>in:likes</code></td><td><code>in:posted</code></td><td><code>in:watching</code></td><td><code>in:tracking</code></td><td><code>in:private</code></td></tr> <tr><td><code>in:likes</code></td><td><code>in:posted</code></td><td><code>in:watching</code></td><td><code>in:tracking</code></td><td><code>in:private</code></td></tr>
<tr><td><code>in:bookmarks</code></td><td><code>in:first</code></td><td colspan=3></td></tr> <tr><td><code>in:bookmarks</code></td><td><code>in:first</code></td><td colspan=3></td></tr>
<tr><td><code>posts_count:num</code></td><td><code>min_age:days</code></td><td><code>max_age:days</code></td> <td colspan=2></td></tr> <tr><td><code>posts_count:num</code></td><td><code>before:days or date</code></td><td><code>after:days or date</code></td> <td colspan=2></td></tr>
</table> </table>
</p> </p>
<h3>Examples</h3> <h3>Examples</h3>

View File

@ -94,6 +94,40 @@ class Search
data data
end end
def self.word_to_date(str)
if str =~ /^[0-9]{1,3}$/
return Time.zone.now.beginning_of_day.days_ago(str.to_i)
end
if str =~ /^([12][0-9]{3})(-([0-1]?[0-9]))?(-([0-3]?[0-9]))?$/
year = $1.to_i
month = $2 ? $3.to_i : 1
day = $4 ? $5.to_i : 1
return if day==0 || month==0 || day > 31 || month > 12
return Time.zone.parse("#{year}-#{month}-#{day}") rescue nil
end
if str.downcase == "yesterday"
return Time.zone.now.beginning_of_day.yesterday
end
titlecase = str.downcase.titlecase
if Date::DAYNAMES.include?(titlecase)
return Time.zone.now.beginning_of_week(str.downcase.to_sym)
end
if idx = (Date::MONTHNAMES.find_index(titlecase) ||
Date::ABBR_MONTHNAMES.find_index(titlecase))
delta = Time.zone.now.month - idx
delta += 12 if delta < 0
Time.zone.now.beginning_of_month.months_ago(delta)
end
end
def initialize(term, opts=nil) def initialize(term, opts=nil)
@opts = opts || {} @opts = opts || {}
@guardian = @opts[:guardian] || Guardian.new @guardian = @opts[:guardian] || Guardian.new
@ -251,14 +285,20 @@ class Search
end end
end end
advanced_filter(/min_age:(\d+)/) do |posts,match| advanced_filter(/before:(.*)/) do |posts,match|
n = match.to_i if date = Search.word_to_date(match)
posts.where("topics.created_at > ?", n.days.ago) posts.where("posts.created_at < ?", date)
else
posts
end
end end
advanced_filter(/max_age:(\d+)/) do |posts,match| advanced_filter(/after:(.*)/) do |posts,match|
n = match.to_i if date = Search.word_to_date(match)
posts.where("topics.created_at < ?", n.days.ago) posts.where("posts.created_at > ?", date)
else
posts
end
end end
private private

View File

@ -408,15 +408,21 @@ describe Search do
describe 'Advanced search' do describe 'Advanced search' do
it 'supports min_age and max_age in:first user:' do it 'supports before and after in:first user:' do
topic = Fabricate(:topic, created_at: 3.months.ago)
Fabricate(:post, raw: 'hi this is a test 123 123', topic: topic) time = Time.zone.parse('2001-05-20 2:55')
freeze_time(time)
topic = Fabricate(:topic)
Fabricate(:post, raw: 'hi this is a test 123 123', topic: topic, created_at: time.months_ago(2))
_post = Fabricate(:post, raw: 'boom boom shake the room', topic: topic) _post = Fabricate(:post, raw: 'boom boom shake the room', topic: topic)
expect(Search.execute('test min_age:100').posts.length).to eq(1) expect(Search.execute('test before:1').posts.length).to eq(1)
expect(Search.execute('test min_age:10').posts.length).to eq(0) expect(Search.execute('test before:2001-04-20').posts.length).to eq(1)
expect(Search.execute('test max_age:10').posts.length).to eq(1) expect(Search.execute('test before:2001').posts.length).to eq(0)
expect(Search.execute('test max_age:100').posts.length).to eq(0) expect(Search.execute('test before:monday').posts.length).to eq(1)
expect(Search.execute('test after:jan').posts.length).to eq(1)
expect(Search.execute('test in:first').posts.length).to eq(1) expect(Search.execute('test in:first').posts.length).to eq(1)
expect(Search.execute('boom').posts.length).to eq(1) expect(Search.execute('boom').posts.length).to eq(1)
@ -512,5 +518,34 @@ describe Search do
Post.exec_sql("SELECT to_tsvector('bbb') @@ " << ts_query) Post.exec_sql("SELECT to_tsvector('bbb') @@ " << ts_query)
end end
context '#word_to_date' do
it 'parses relative dates correctly' do
time = Time.zone.parse('2001-02-20 2:55')
freeze_time(time)
expect(Search.word_to_date('yesterday')).to eq(time.beginning_of_day.yesterday)
expect(Search.word_to_date('suNday')).to eq(Time.zone.parse('2001-02-18'))
expect(Search.word_to_date('thursday')).to eq(Time.zone.parse('2001-02-15'))
expect(Search.word_to_date('deCember')).to eq(Time.zone.parse('2000-12-01'))
expect(Search.word_to_date('deC')).to eq(Time.zone.parse('2000-12-01'))
expect(Search.word_to_date('january')).to eq(Time.zone.parse('2001-01-01'))
expect(Search.word_to_date('jan')).to eq(Time.zone.parse('2001-01-01'))
expect(Search.word_to_date('100')).to eq(time.beginning_of_day.days_ago(100))
expect(Search.word_to_date('invalid')).to eq(nil)
end
it 'parses absolute dates correctly' do
expect(Search.word_to_date('2001-1-20')).to eq(Time.zone.parse('2001-01-20'))
expect(Search.word_to_date('2030-10-2')).to eq(Time.zone.parse('2030-10-02'))
expect(Search.word_to_date('2030-10')).to eq(Time.zone.parse('2030-10-01'))
expect(Search.word_to_date('2030')).to eq(Time.zone.parse('2030-01-01'))
expect(Search.word_to_date('2030-01-32')).to eq(nil)
expect(Search.word_to_date('10000')).to eq(nil)
end
end
end end