display 'similar to' earlier when composing a post
This commit is contained in:
parent
32972de2d7
commit
8ed318c4fe
|
@ -119,18 +119,15 @@ export default Ember.Component.extend({
|
||||||
// We don't care about similar topics unless creating a topic
|
// We don't care about similar topics unless creating a topic
|
||||||
if (!composer.get('creatingTopic')) { return; }
|
if (!composer.get('creatingTopic')) { return; }
|
||||||
|
|
||||||
const origBody = composer.get('reply') || '';
|
// TODO pass the 200 in from somewhere
|
||||||
|
const raw = (composer.get('reply') || '').substr(0, 200);
|
||||||
const title = composer.get('title') || '';
|
const title = composer.get('title') || '';
|
||||||
|
|
||||||
// Ensure the fields are of the minimum length
|
// Ensure we have at least a title
|
||||||
if (origBody.length < Discourse.SiteSettings.min_body_similar_length) { return; }
|
if (title.length < this.siteSettings.min_title_similar_length) { return; }
|
||||||
if (title.length < Discourse.SiteSettings.min_title_similar_length) { return; }
|
|
||||||
|
|
||||||
// TODO pass the 200 in from somewhere
|
|
||||||
const body = origBody.substr(0, 200);
|
|
||||||
|
|
||||||
// Don't search over and over
|
// Don't search over and over
|
||||||
const concat = title + body;
|
const concat = title + raw;
|
||||||
if (concat === this._lastSimilaritySearch) { return; }
|
if (concat === this._lastSimilaritySearch) { return; }
|
||||||
this._lastSimilaritySearch = concat;
|
this._lastSimilaritySearch = concat;
|
||||||
|
|
||||||
|
@ -143,14 +140,15 @@ export default Ember.Component.extend({
|
||||||
|
|
||||||
this._similarTopicsMessage = message;
|
this._similarTopicsMessage = message;
|
||||||
|
|
||||||
composer.store.find('similar-topic', {title, raw: body}).then(newTopics => {
|
composer.store.find('similar-topic', { title, raw }).then(topics => {
|
||||||
similarTopics.clear();
|
similarTopics.clear();
|
||||||
similarTopics.pushObjects(newTopics.get('content'));
|
similarTopics.pushObjects(topics.get('content'));
|
||||||
|
|
||||||
if (similarTopics.get('length') > 0) {
|
if (similarTopics.get('length') > 0) {
|
||||||
message.set('similarTopics', similarTopics);
|
message.set('similarTopics', similarTopics);
|
||||||
this.send('popup', message);
|
this.send('popup', message);
|
||||||
} else if (message) {
|
} else if (message) {
|
||||||
|
debugger
|
||||||
this.send('hideMessage', message);
|
this.send('hideMessage', message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,25 +16,16 @@ class SimilarTopicsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
params.require(:title)
|
title = params.require(:title)
|
||||||
params.require(:raw)
|
raw = params[:raw]
|
||||||
title, raw = params[:title], params[:raw]
|
|
||||||
invalid_length = [:title, :raw].any? { |key| check_invalid_length(key, params[key]) }
|
|
||||||
|
|
||||||
# Only suggest similar topics if the site has a minimum amount of topics present
|
if title.length < SiteSetting.min_title_similar_length || !Topic.count_exceeds_minimum?
|
||||||
# and params are long enough.
|
return render json: []
|
||||||
return render json: [] if invalid_length || !Topic.count_exceeds_minimum?
|
end
|
||||||
|
|
||||||
topics = Topic.similar_to(title, raw, current_user).to_a
|
topics = Topic.similar_to(title, raw, current_user).to_a
|
||||||
topics.map! { |t| SimilarTopic.new(t) }
|
topics.map! { |t| SimilarTopic.new(t) }
|
||||||
render_serialized(topics, SimilarTopicSerializer, root: :similar_topics, rest_serializer: true)
|
render_serialized(topics, SimilarTopicSerializer, root: :similar_topics, rest_serializer: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def check_invalid_length(key, attr)
|
|
||||||
str = (key == :raw) ? "body" : key.to_s
|
|
||||||
attr.length < SiteSetting.send("min_#{str}_similar_length")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -436,42 +436,46 @@ class Topic < ActiveRecord::Base
|
||||||
archetype == Archetype.private_message
|
archetype == Archetype.private_message
|
||||||
end
|
end
|
||||||
|
|
||||||
MAX_SIMILAR_BODY_LENGTH = 200
|
MAX_SIMILAR_BODY_LENGTH ||= 200
|
||||||
# Search for similar topics
|
|
||||||
def self.similar_to(title, raw, user = nil)
|
|
||||||
return [] unless title.present?
|
|
||||||
return [] unless raw.present?
|
|
||||||
|
|
||||||
filter_words = Search.prepare_data(title + " " + raw[0...MAX_SIMILAR_BODY_LENGTH]);
|
def self.similar_to(title, raw, user = nil)
|
||||||
|
return [] if title.blank?
|
||||||
|
raw = raw.presence || ""
|
||||||
|
|
||||||
|
search_data = "#{title} #{raw[0...MAX_SIMILAR_BODY_LENGTH]}".strip
|
||||||
|
filter_words = Search.prepare_data(search_data)
|
||||||
ts_query = Search.ts_query(filter_words, nil, "|")
|
ts_query = Search.ts_query(filter_words, nil, "|")
|
||||||
|
|
||||||
# Exclude category definitions from similar topic suggestions
|
candidates = Topic
|
||||||
|
.visible
|
||||||
candidates = Topic.visible
|
|
||||||
.secured(Guardian.new(user))
|
|
||||||
.listable_topics
|
.listable_topics
|
||||||
.joins('JOIN topic_search_data s ON topics.id = s.topic_id')
|
.secured(Guardian.new(user))
|
||||||
|
.joins("JOIN topic_search_data s ON topics.id = s.topic_id")
|
||||||
|
.joins("LEFT JOIN categories c ON topics.id = c.topic_id")
|
||||||
.where("search_data @@ #{ts_query}")
|
.where("search_data @@ #{ts_query}")
|
||||||
|
.where("c.topic_id IS NULL")
|
||||||
.order("ts_rank(search_data, #{ts_query}) DESC")
|
.order("ts_rank(search_data, #{ts_query}) DESC")
|
||||||
.limit(SiteSetting.max_similar_results * 3)
|
.limit(SiteSetting.max_similar_results * 3)
|
||||||
|
|
||||||
exclude_topic_ids = Category.pluck(:topic_id).compact!
|
|
||||||
if exclude_topic_ids.present?
|
|
||||||
candidates = candidates.where("topics.id NOT IN (?)", exclude_topic_ids)
|
|
||||||
end
|
|
||||||
|
|
||||||
candidate_ids = candidates.pluck(:id)
|
candidate_ids = candidates.pluck(:id)
|
||||||
|
|
||||||
return [] unless candidate_ids.present?
|
return [] if candidate_ids.blank?
|
||||||
|
|
||||||
similar = Topic.select(sanitize_sql_array(["topics.*, similarity(topics.title, :title) + similarity(topics.title, :raw) AS similarity, p.cooked as blurb", title: title, raw: raw]))
|
similars = Topic
|
||||||
.joins("JOIN posts AS p ON p.topic_id = topics.id AND p.post_number = 1")
|
.joins("JOIN posts AS p ON p.topic_id = topics.id AND p.post_number = 1")
|
||||||
.limit(SiteSetting.max_similar_results)
|
|
||||||
.where("topics.id IN (?)", candidate_ids)
|
.where("topics.id IN (?)", candidate_ids)
|
||||||
.where("similarity(topics.title, :title) + similarity(topics.title, :raw) > 0.2", raw: raw, title: title)
|
.order("similarity DESC")
|
||||||
.order('similarity desc')
|
.limit(SiteSetting.max_similar_results)
|
||||||
|
|
||||||
similar
|
if raw.present?
|
||||||
|
similars
|
||||||
|
.select(sanitize_sql_array(["topics.*, similarity(topics.title, :title) + similarity(p.raw, :raw) AS similarity, p.cooked AS blurb", title: title, raw: raw]))
|
||||||
|
.where("similarity(topics.title, :title) + similarity(p.raw, :raw) > 0.2", title: title, raw: raw)
|
||||||
|
else
|
||||||
|
similars
|
||||||
|
.select(sanitize_sql_array(["topics.*, similarity(topics.title, :title) AS similarity, p.cooked AS blurb", title: title]))
|
||||||
|
.where("similarity(topics.title, :title) > 0.2", title: title)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_status(status, enabled, user, opts = {})
|
def update_status(status, enabled, user, opts = {})
|
||||||
|
@ -481,7 +485,7 @@ class Topic < ActiveRecord::Base
|
||||||
|
|
||||||
# Atomically creates the next post number
|
# Atomically creates the next post number
|
||||||
def self.next_post_number(topic_id, reply = false, whisper = false)
|
def self.next_post_number(topic_id, reply = false, whisper = false)
|
||||||
highest = exec_sql("select coalesce(max(post_number),0) as max from posts where topic_id = ?", topic_id).first['max'].to_i
|
highest = exec_sql("SELECT coalesce(max(post_number),0) AS max FROM posts WHERE topic_id = ?", topic_id).first['max'].to_i
|
||||||
|
|
||||||
if whisper
|
if whisper
|
||||||
|
|
||||||
|
|
|
@ -499,12 +499,6 @@ posting:
|
||||||
locale_default:
|
locale_default:
|
||||||
zh_CN: 4
|
zh_CN: 4
|
||||||
zh_TW: 4
|
zh_TW: 4
|
||||||
min_body_similar_length:
|
|
||||||
client: true
|
|
||||||
default: 15
|
|
||||||
locale_default:
|
|
||||||
zh_CN: 5
|
|
||||||
zh_TW: 5
|
|
||||||
enable_private_messages:
|
enable_private_messages:
|
||||||
default: true
|
default: true
|
||||||
client: true
|
client: true
|
||||||
|
|
|
@ -50,9 +50,7 @@ class Search
|
||||||
data = search_data.squish
|
data = search_data.squish
|
||||||
# TODO cppjieba_rb is designed for chinese, we need something else for Korean / Japanese
|
# TODO cppjieba_rb is designed for chinese, we need something else for Korean / Japanese
|
||||||
if ['zh_TW', 'zh_CN', 'ja', 'ko'].include?(SiteSetting.default_locale) || SiteSetting.search_tokenize_chinese_japanese_korean
|
if ['zh_TW', 'zh_CN', 'ja', 'ko'].include?(SiteSetting.default_locale) || SiteSetting.search_tokenize_chinese_japanese_korean
|
||||||
unless defined? CppjiebaRb
|
require 'cppjieba_rb' unless defined? CppjiebaRb
|
||||||
require 'cppjieba_rb'
|
|
||||||
end
|
|
||||||
mode = (purpose == :query ? :query : :mix)
|
mode = (purpose == :query ? :query : :mix)
|
||||||
data = CppjiebaRb.segment(search_data, mode: mode)
|
data = CppjiebaRb.segment(search_data, mode: mode)
|
||||||
data = CppjiebaRb.filter_stop_word(data).join(' ')
|
data = CppjiebaRb.filter_stop_word(data).join(' ')
|
||||||
|
|
|
@ -10,10 +10,6 @@ describe SimilarTopicsController do
|
||||||
expect { xhr :get, :index, raw: raw }.to raise_error(ActionController::ParameterMissing)
|
expect { xhr :get, :index, raw: raw }.to raise_error(ActionController::ParameterMissing)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "requires a raw body" do
|
|
||||||
expect { xhr :get, :index, title: title }.to raise_error(ActionController::ParameterMissing)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns no results if the title length is below the minimum" do
|
it "returns no results if the title length is below the minimum" do
|
||||||
Topic.expects(:similar_to).never
|
Topic.expects(:similar_to).never
|
||||||
SiteSetting.min_title_similar_length = 100
|
SiteSetting.min_title_similar_length = 100
|
||||||
|
@ -22,14 +18,6 @@ describe SimilarTopicsController do
|
||||||
expect(json["similar_topics"].size).to eq(0)
|
expect(json["similar_topics"].size).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns no results if the body length is below the minimum" do
|
|
||||||
Topic.expects(:similar_to).never
|
|
||||||
SiteSetting.min_body_similar_length = 100
|
|
||||||
xhr :get, :index, title: title, raw: raw
|
|
||||||
json = ::JSON.parse(response.body)
|
|
||||||
expect(json["similar_topics"].size).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "minimum_topics_similar" do
|
describe "minimum_topics_similar" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -48,7 +48,6 @@ Discourse.SiteSettingsOriginal = {
|
||||||
"min_private_message_title_length":2,
|
"min_private_message_title_length":2,
|
||||||
"allow_uncategorized_topics":true,
|
"allow_uncategorized_topics":true,
|
||||||
"min_title_similar_length":10,
|
"min_title_similar_length":10,
|
||||||
"min_body_similar_length":15,
|
|
||||||
"edit_history_visible_to_public":true,
|
"edit_history_visible_to_public":true,
|
||||||
"delete_removed_posts_after":24,
|
"delete_removed_posts_after":24,
|
||||||
"traditional_markdown_linebreaks":false,
|
"traditional_markdown_linebreaks":false,
|
||||||
|
|
Loading…
Reference in New Issue