mirror of
https://github.com/discourse/discourse.git
synced 2025-02-19 01:46:01 +00:00
FEATURE: search log term details page (#5445)
This commit is contained in:
parent
bbc606988f
commit
eab66065d1
@ -0,0 +1,13 @@
|
|||||||
|
export default Ember.Controller.extend({
|
||||||
|
loading: false,
|
||||||
|
term: null,
|
||||||
|
period: "yearly",
|
||||||
|
searchType: "all",
|
||||||
|
|
||||||
|
searchTypeOptions: [
|
||||||
|
{id: 'all', name: I18n.t('admin.logs.search_logs.types.all_search_types')},
|
||||||
|
{id: 'header', name: I18n.t('admin.logs.search_logs.types.header')},
|
||||||
|
{id: 'full_page', name: I18n.t('admin.logs.search_logs.types.full_page')},
|
||||||
|
{id: 'click_through_only', name: I18n.t('admin.logs.search_logs.types.click_through_only')}
|
||||||
|
]
|
||||||
|
});
|
@ -66,7 +66,10 @@ export default function() {
|
|||||||
this.route('screenedEmails', { path: '/screened_emails' });
|
this.route('screenedEmails', { path: '/screened_emails' });
|
||||||
this.route('screenedIpAddresses', { path: '/screened_ip_addresses' });
|
this.route('screenedIpAddresses', { path: '/screened_ip_addresses' });
|
||||||
this.route('screenedUrls', { path: '/screened_urls' });
|
this.route('screenedUrls', { path: '/screened_urls' });
|
||||||
this.route('searchLogs', { path: '/search_logs' });
|
this.route('adminSearchLogs', { path: '/search_logs', resetNamespace: true}, function() {
|
||||||
|
this.route('index', { path: '/' });
|
||||||
|
this.route('term', { path: '/term/:term' });
|
||||||
|
});
|
||||||
this.route('adminWatchedWords', { path: '/watched_words', resetNamespace: true}, function() {
|
this.route('adminWatchedWords', { path: '/watched_words', resetNamespace: true}, function() {
|
||||||
this.route('index', { path: '/' });
|
this.route('index', { path: '/' });
|
||||||
this.route('action', { path: '/action/:action_id' });
|
this.route('action', { path: '/action/:action_id' });
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { ajax } from 'discourse/lib/ajax';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
|
||||||
export default Discourse.Route.extend({
|
export default Discourse.Route.extend({
|
||||||
renderTemplate() {
|
|
||||||
this.render('admin/templates/logs/search-logs', {into: 'adminLogs'});
|
|
||||||
},
|
|
||||||
|
|
||||||
queryParams: {
|
queryParams: {
|
||||||
period: { refreshModel: true },
|
period: { refreshModel: true },
|
||||||
searchType: { refreshModel: true }
|
searchType: { refreshModel: true }
|
@ -0,0 +1,33 @@
|
|||||||
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
queryParams: {
|
||||||
|
period: { refreshModel: true },
|
||||||
|
searchType: { refreshModel: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
this._params = params;
|
||||||
|
|
||||||
|
return ajax(`/admin/logs/search_logs/term/${params.term}.json`, {
|
||||||
|
data: {
|
||||||
|
period: params.period,
|
||||||
|
search_type: params.searchType
|
||||||
|
}
|
||||||
|
}).then(json => {
|
||||||
|
const model = Ember.Object.create({ type: "search_log_term" });
|
||||||
|
model.setProperties(json.term);
|
||||||
|
return model;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
const params = this._params;
|
||||||
|
controller.setProperties({
|
||||||
|
model,
|
||||||
|
term: params.term,
|
||||||
|
period: params.period,
|
||||||
|
searchType: params.searchType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -4,7 +4,7 @@
|
|||||||
{{nav-item route='adminLogs.screenedIpAddresses' label='admin.logs.screened_ips.title'}}
|
{{nav-item route='adminLogs.screenedIpAddresses' label='admin.logs.screened_ips.title'}}
|
||||||
{{nav-item route='adminLogs.screenedUrls' label='admin.logs.screened_urls.title'}}
|
{{nav-item route='adminLogs.screenedUrls' label='admin.logs.screened_urls.title'}}
|
||||||
{{nav-item route='adminWatchedWords' label='admin.watched_words.title'}}
|
{{nav-item route='adminWatchedWords' label='admin.watched_words.title'}}
|
||||||
{{nav-item route='adminLogs.searchLogs' label='admin.logs.search_logs.title'}}
|
{{nav-item route='adminSearchLogs' label='admin.logs.search_logs.title'}}
|
||||||
{{#if currentUser.admin}}
|
{{#if currentUser.admin}}
|
||||||
{{nav-item path='/logs' label='admin.logs.logster.title'}}
|
{{nav-item path='/logs' label='admin.logs.logster.title'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
|
|
||||||
{{#each model as |item|}}
|
{{#each model as |item|}}
|
||||||
<div class="admin-list-item">
|
<div class="admin-list-item">
|
||||||
<div class="col term">{{item.term}}</div>
|
<div class="col term">
|
||||||
|
{{#link-to 'adminSearchLogs.term' item.term}}{{item.term}}{{/link-to}}
|
||||||
|
</div>
|
||||||
<div class="col">{{item.searches}}</div>
|
<div class="col">{{item.searches}}</div>
|
||||||
<div class="col">{{item.click_through}}</div>
|
<div class="col">{{item.click_through}}</div>
|
||||||
<div class="col">{{item.unique}}</div>
|
<div class="col">{{item.unique}}</div>
|
13
app/assets/javascripts/admin/templates/search-logs-term.hbs
Normal file
13
app/assets/javascripts/admin/templates/search-logs-term.hbs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<p>
|
||||||
|
{{period-chooser period=period}}
|
||||||
|
{{combo-box content=searchTypeOptions value=searchType class='search-logs-filter'}}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
{{#link-to 'full-page-search' (query-params q=term)}}{{term}}{{/link-to}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{{#conditional-loading-spinner condition=refreshing}}
|
||||||
|
{{admin-graph model=model}}
|
||||||
|
{{/conditional-loading-spinner}}
|
@ -6,4 +6,17 @@ class Admin::SearchLogsController < Admin::AdminController
|
|||||||
render_serialized(SearchLog.trending(period&.to_sym, search_type&.to_sym), SearchLogsSerializer)
|
render_serialized(SearchLog.trending(period&.to_sym, search_type&.to_sym), SearchLogsSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def term
|
||||||
|
params.require(:term)
|
||||||
|
|
||||||
|
term = params[:term]
|
||||||
|
period = params[:period] || "yearly"
|
||||||
|
search_type = params[:search_type] || "all"
|
||||||
|
|
||||||
|
details = SearchLog.term_details(term, period&.to_sym, search_type&.to_sym)
|
||||||
|
raise Discourse::NotFound if details.blank?
|
||||||
|
|
||||||
|
render_json_dump(term: details)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -57,6 +57,30 @@ class SearchLog < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.term_details(term, period = :weekly, search_type = :all)
|
||||||
|
details = []
|
||||||
|
|
||||||
|
result = SearchLog.select("COUNT(*) AS count, created_at::date AS date")
|
||||||
|
.where('term LIKE ?', term)
|
||||||
|
.where('created_at > ?', start_of(period))
|
||||||
|
|
||||||
|
result = result.where('search_type = ?', search_types[search_type]) if search_type == :header || search_type == :full_page
|
||||||
|
result = result.where('search_result_id IS NOT NULL') if search_type == :click_through_only
|
||||||
|
|
||||||
|
result.group(:term)
|
||||||
|
.order("date")
|
||||||
|
.group("date")
|
||||||
|
.each do |record|
|
||||||
|
details << { x: Date.parse(record['date'].to_s), y: record['count'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "search_log_term",
|
||||||
|
title: I18n.t("search_logs.graph_title"),
|
||||||
|
data: details
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def self.trending(period = :all, search_type = :all)
|
def self.trending(period = :all, search_type = :all)
|
||||||
result = SearchLog.select("term,
|
result = SearchLog.select("term,
|
||||||
COUNT(*) AS searches,
|
COUNT(*) AS searches,
|
||||||
|
@ -3252,6 +3252,7 @@ en:
|
|||||||
all_search_types: "All search types"
|
all_search_types: "All search types"
|
||||||
header: "Header"
|
header: "Header"
|
||||||
full_page: "Full Page"
|
full_page: "Full Page"
|
||||||
|
click_through_only: "All (click through only)"
|
||||||
logster:
|
logster:
|
||||||
title: "Error Logs"
|
title: "Error Logs"
|
||||||
|
|
||||||
|
@ -3660,3 +3660,6 @@ en:
|
|||||||
description: |
|
description: |
|
||||||
<p>If you ever feel like changing these settings, visit <a href='/admin' target='_blank'>your admin section</a>; find it next to the wrench icon in the site menu.</p>
|
<p>If you ever feel like changing these settings, visit <a href='/admin' target='_blank'>your admin section</a>; find it next to the wrench icon in the site menu.</p>
|
||||||
<p>Have fun, and good luck <a href='https://blog.discourse.org/2014/08/building-a-discourse-community/' target='_blank'>building your new community!</a></p>
|
<p>Have fun, and good luck <a href='https://blog.discourse.org/2014/08/building-a-discourse-community/' target='_blank'>building your new community!</a></p>
|
||||||
|
|
||||||
|
search_logs:
|
||||||
|
graph_title: "Search Count"
|
||||||
|
@ -177,6 +177,7 @@ Discourse::Application.routes.draw do
|
|||||||
end
|
end
|
||||||
post "watched_words/upload" => "watched_words#upload"
|
post "watched_words/upload" => "watched_words#upload"
|
||||||
resources :search_logs, only: [:index]
|
resources :search_logs, only: [:index]
|
||||||
|
get 'search_logs/term/:term' => 'search_logs#term'
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/logs" => "staff_action_logs#index"
|
get "/logs" => "staff_action_logs#index"
|
||||||
|
@ -156,6 +156,26 @@ RSpec.describe SearchLog, type: :model do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "term_details" do
|
||||||
|
before do
|
||||||
|
SearchLog.log(term: "ruby", search_type: :header, ip_address: "127.0.0.1")
|
||||||
|
SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.1', user_id: Fabricate(:user).id)
|
||||||
|
SearchLog.log(term: "ruby", search_type: :full_page, ip_address: "127.0.0.2")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "correctly returns term details" do
|
||||||
|
term_details = SearchLog.term_details("ruby")
|
||||||
|
expect(term_details[:data][0][:y]).to eq(3)
|
||||||
|
|
||||||
|
term_header_details = SearchLog.term_details("ruby", :all, :header)
|
||||||
|
expect(term_header_details[:data][0][:y]).to eq(2)
|
||||||
|
|
||||||
|
SearchLog.where(term: 'ruby', ip_address: '127.0.0.2').update_all(search_result_id: 24)
|
||||||
|
term_click_through_details = SearchLog.term_details("ruby", :all, :click_through_only)
|
||||||
|
expect(term_click_through_details[:data][0][:y]).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "trending" do
|
context "trending" do
|
||||||
before do
|
before do
|
||||||
SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.1')
|
SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.1')
|
||||||
|
@ -32,4 +32,29 @@ RSpec.describe Admin::SearchLogsController do
|
|||||||
expect(json[0]['term']).to eq('ruby')
|
expect(json[0]['term']).to eq('ruby')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "#term" do
|
||||||
|
it "raises an error if you aren't logged in" do
|
||||||
|
expect do
|
||||||
|
get '/admin/logs/search_logs/term/ruby.json'
|
||||||
|
end.to raise_error(ActionController::RoutingError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if you aren't an admin" do
|
||||||
|
sign_in(user)
|
||||||
|
expect do
|
||||||
|
get '/admin/logs/search_logs/term/ruby.json'
|
||||||
|
end.to raise_error(ActionController::RoutingError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should work if you are an admin" do
|
||||||
|
sign_in(admin)
|
||||||
|
get '/admin/logs/search_logs/term/ruby.json'
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
json = ::JSON.parse(response.body)
|
||||||
|
expect(json['term']['type']).to eq('search_log_term')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
acceptance("Admin - Search Log Term", { loggedIn: true });
|
||||||
|
|
||||||
|
QUnit.test("show search log term details", assert => {
|
||||||
|
visit("/admin/logs/search_logs/term/ruby");
|
||||||
|
andThen(() => {
|
||||||
|
assert.ok($('div.search-logs-filter').length, "has the search type filter");
|
||||||
|
assert.ok(exists('iframe.chartjs-hidden-iframe'), "has graph iframe");
|
||||||
|
});
|
||||||
|
});
|
@ -401,6 +401,12 @@ export default function() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get('/admin/logs/search_logs/term/ruby.json', () => {
|
||||||
|
return response(200, {
|
||||||
|
"term":{"type":"search_log_term","title":"Search Count","data":[{"x":"2017-07-20","y":2}]}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.get('/onebox', request => {
|
this.get('/onebox', request => {
|
||||||
if (request.queryParams.url === 'http://www.example.com/has-title.html' ||
|
if (request.queryParams.url === 'http://www.example.com/has-title.html' ||
|
||||||
request.queryParams.url === 'http://www.example.com/has-title-and-a-url-that-is-more-than-80-characters-because-thats-good-for-seo-i-guess.html') {
|
request.queryParams.url === 'http://www.example.com/has-title-and-a-url-that-is-more-than-80-characters-because-thats-good-for-seo-i-guess.html') {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user