Track clicks on topics in search results
This commit is contained in:
parent
21e02d6969
commit
cdb3706025
|
@ -4,6 +4,7 @@ import { avatarImg } from 'discourse/widgets/post';
|
|||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { wantsNewWindow } from 'discourse/lib/intercept-click';
|
||||
import { applySearchAutocomplete } from "discourse/lib/search";
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
|
@ -249,7 +250,29 @@ export default createWidget('header', {
|
|||
},
|
||||
|
||||
linkClickedEvent(attrs) {
|
||||
if (!(attrs && attrs.searchContextEnabled)) this.closeAll();
|
||||
|
||||
let searchContextEnabled = false;
|
||||
if (attrs) {
|
||||
searchContextEnabled = attrs.searchContextEnabled;
|
||||
|
||||
const { searchLogId, searchResultId, searchResultType } = attrs;
|
||||
if (searchLogId && searchResultId && searchResultType) {
|
||||
|
||||
ajax('/search/click', {
|
||||
type: 'POST',
|
||||
data: {
|
||||
search_log_id: searchLogId,
|
||||
search_result_id: searchResultId,
|
||||
search_result_type: searchResultType
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchContextEnabled) {
|
||||
this.closeAll();
|
||||
}
|
||||
|
||||
this.updateHighlight();
|
||||
},
|
||||
|
||||
|
|
|
@ -20,18 +20,26 @@ class Highlighted extends RawHtml {
|
|||
}
|
||||
}
|
||||
|
||||
function createSearchResult(type, linkField, fn) {
|
||||
function createSearchResult({ type, linkField, builder }) {
|
||||
return createWidget(`search-result-${type}`, {
|
||||
html(attrs) {
|
||||
return attrs.results.map(r => {
|
||||
|
||||
let searchResultId;
|
||||
if (type === "topic") {
|
||||
searchResultId = r.get('topic_id');
|
||||
}
|
||||
return h('li', this.attach('link', {
|
||||
href: r.get(linkField),
|
||||
contents: () => fn.call(this, r, attrs.term),
|
||||
contents: () => builder.call(this, r, attrs.term),
|
||||
className: 'search-link',
|
||||
searchContextEnabled: this.attrs.searchContextEnabled
|
||||
searchResultId,
|
||||
searchResultType: type,
|
||||
searchContextEnabled: attrs.searchContextEnabled,
|
||||
searchLogId: attrs.searchLogId
|
||||
}));
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,27 +55,43 @@ function postResult(result, link, term) {
|
|||
return html;
|
||||
}
|
||||
|
||||
createSearchResult('user', 'path', function(u) {
|
||||
return [ avatarImg('small', { template: u.avatar_template, username: u.username }), ' ', h('span.user-results', h('b', u.username)), ' ', h('span.user-results', u.name ? u.name : '') ];
|
||||
createSearchResult({
|
||||
type: 'user',
|
||||
linkField: 'path',
|
||||
builder(u) {
|
||||
return [ avatarImg('small', { template: u.avatar_template, username: u.username }), ' ', h('span.user-results', h('b', u.username)), ' ', h('span.user-results', u.name ? u.name : '') ];
|
||||
}
|
||||
});
|
||||
|
||||
createSearchResult('topic', 'url', function(result, term) {
|
||||
const topic = result.topic;
|
||||
const link = h('span.topic', [
|
||||
this.attach('topic-status', { topic, disableActions: true }),
|
||||
h('span.topic-title', new Highlighted(topic.get('fancyTitle'), term)),
|
||||
this.attach('category-link', { category: topic.get('category'), link: false })
|
||||
]);
|
||||
createSearchResult({
|
||||
type: 'topic',
|
||||
linkField: 'url',
|
||||
builder(result, term) {
|
||||
const topic = result.topic;
|
||||
const link = h('span.topic', [
|
||||
this.attach('topic-status', { topic, disableActions: true }),
|
||||
h('span.topic-title', new Highlighted(topic.get('fancyTitle'), term)),
|
||||
this.attach('category-link', { category: topic.get('category'), link: false })
|
||||
]);
|
||||
|
||||
return postResult.call(this, result, link, term);
|
||||
return postResult.call(this, result, link, term);
|
||||
}
|
||||
});
|
||||
|
||||
createSearchResult('post', 'url', function(result, term) {
|
||||
return postResult.call(this, result, I18n.t('search.post_format', result), term);
|
||||
createSearchResult({
|
||||
type: 'post',
|
||||
linkField: 'url',
|
||||
builder(result, term) {
|
||||
return postResult.call(this, result, I18n.t('search.post_format', result), term);
|
||||
}
|
||||
});
|
||||
|
||||
createSearchResult('category', 'url', function (c) {
|
||||
return this.attach('category-link', { category: c, link: false });
|
||||
createSearchResult({
|
||||
type: 'category',
|
||||
linkField: 'url',
|
||||
builder(c) {
|
||||
return this.attach('category-link', { category: c, link: false });
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('search-menu-results', {
|
||||
|
@ -102,7 +126,8 @@ createWidget('search-menu-results', {
|
|||
|
||||
return [
|
||||
h('ul', this.attach(rt.componentName, {
|
||||
searchContextEnabled: this.attrs.searchContextEnabled,
|
||||
searchContextEnabled: attrs.searchContextEnabled,
|
||||
searchLogId: attrs.results.grouped_search_result.search_log_id,
|
||||
results: rt.results,
|
||||
term: attrs.term
|
||||
})),
|
||||
|
|
|
@ -127,11 +127,13 @@ export default createWidget('search-menu', {
|
|||
if (searchData.loading) {
|
||||
results.push(h('div.searching', h('div.spinner')));
|
||||
} else {
|
||||
results.push(this.attach('search-menu-results', { term: searchData.term,
|
||||
noResults: searchData.noResults,
|
||||
results: searchData.results,
|
||||
invalidTerm: searchData.invalidTerm,
|
||||
searchContextEnabled: searchData.contextEnabled }));
|
||||
results.push(this.attach('search-menu-results', {
|
||||
term: searchData.term,
|
||||
noResults: searchData.noResults,
|
||||
results: searchData.results,
|
||||
invalidTerm: searchData.invalidTerm,
|
||||
searchContextEnabled: searchData.contextEnabled
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,27 @@ class SearchController < ApplicationController
|
|||
render_serialized(result, GroupedSearchResultSerializer, result: result)
|
||||
end
|
||||
|
||||
def click
|
||||
params.require(:search_log_id)
|
||||
params.require(:search_result_type)
|
||||
params.require(:search_result_id)
|
||||
|
||||
if params[:search_result_type] == 'topic'
|
||||
where = { id: params[:search_log_id] }
|
||||
if current_user.present?
|
||||
where[:user_id] = current_user.id
|
||||
else
|
||||
where[:ip_address] = request.remote_ip
|
||||
end
|
||||
|
||||
SearchLog.where(where).update_all(
|
||||
clicked_topic_id: params[:search_result_id]
|
||||
)
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def lookup_search_context
|
||||
|
|
|
@ -8,4 +8,8 @@ class GroupedSearchResultSerializer < ApplicationSerializer
|
|||
object.search_log_id
|
||||
end
|
||||
|
||||
def include_search_log_id?
|
||||
search_log_id.present?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -550,6 +550,7 @@ Discourse::Application.routes.draw do
|
|||
get "top" => "list#top"
|
||||
get "search/query" => "search#query"
|
||||
get "search" => "search#show"
|
||||
post "search/click" => "search#click"
|
||||
|
||||
# Topics resource
|
||||
get "t/:id" => "topics#show"
|
||||
|
|
|
@ -123,4 +123,89 @@ describe SearchController do
|
|||
|
||||
end
|
||||
|
||||
context "#click" do
|
||||
it "doesn't work wthout the necessary parameters" do
|
||||
expect(-> {
|
||||
xhr :post, :click
|
||||
}).to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
|
||||
it "doesn't record the click for a different user" do
|
||||
log_in(:user)
|
||||
|
||||
_, search_log_id = SearchLog.log(
|
||||
term: 'kitty',
|
||||
search_type: :header,
|
||||
user_id: -10,
|
||||
ip_address: '127.0.0.1'
|
||||
)
|
||||
|
||||
xhr :post, :click, {
|
||||
search_log_id: search_log_id,
|
||||
search_result_id: 12345,
|
||||
search_result_type: 'topic'
|
||||
}
|
||||
expect(response).to be_success
|
||||
|
||||
expect(SearchLog.find(search_log_id).clicked_topic_id).to be_blank
|
||||
end
|
||||
|
||||
it "records the click for a logged in user" do
|
||||
user = log_in(:user)
|
||||
|
||||
_, search_log_id = SearchLog.log(
|
||||
term: 'kitty',
|
||||
search_type: :header,
|
||||
user_id: user.id,
|
||||
ip_address: '127.0.0.1'
|
||||
)
|
||||
|
||||
xhr :post, :click, {
|
||||
search_log_id: search_log_id,
|
||||
search_result_id: 12345,
|
||||
search_result_type: 'topic'
|
||||
}
|
||||
expect(response).to be_success
|
||||
|
||||
expect(SearchLog.find(search_log_id).clicked_topic_id).to eq(12345)
|
||||
end
|
||||
|
||||
it "records the click for an anonymous user" do
|
||||
request.stubs(:remote_ip).returns('192.168.0.1')
|
||||
|
||||
_, search_log_id = SearchLog.log(
|
||||
term: 'kitty',
|
||||
search_type: :header,
|
||||
ip_address: '192.168.0.1'
|
||||
)
|
||||
|
||||
xhr :post, :click, {
|
||||
search_log_id: search_log_id,
|
||||
search_result_id: 22222,
|
||||
search_result_type: 'topic'
|
||||
}
|
||||
expect(response).to be_success
|
||||
|
||||
expect(SearchLog.find(search_log_id).clicked_topic_id).to eq(22222)
|
||||
end
|
||||
|
||||
it "doesn't record the click for a different IP" do
|
||||
request.stubs(:remote_ip).returns('192.168.0.2')
|
||||
|
||||
_, search_log_id = SearchLog.log(
|
||||
term: 'kitty',
|
||||
search_type: :header,
|
||||
ip_address: '192.168.0.1'
|
||||
)
|
||||
|
||||
xhr :post, :click, {
|
||||
search_log_id: search_log_id,
|
||||
search_result_id: 22222,
|
||||
search_result_type: 'topic'
|
||||
}
|
||||
expect(response).to be_success
|
||||
|
||||
expect(SearchLog.find(search_log_id).clicked_topic_id).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,4 +72,4 @@ QUnit.test("Search with context", assert => {
|
|||
andThen(() => {
|
||||
assert.ok(!$('.search-context input[type=checkbox]').is(":checked"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue