intel 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +118 -19
- data/app/controllers/intel/searches_controller.rb +72 -0
- data/app/views/intel/searches/index.html.erb +27 -0
- data/app/views/intel/searches/overview.html.erb +48 -0
- data/app/views/intel/searches/recent.html.erb +20 -0
- data/app/views/intel/searches/stream.html.erb +13 -0
- data/app/views/layouts/intel/application.html.erb +291 -0
- data/config/routes.rb +7 -0
- data/intel.gemspec +2 -1
- data/lib/generators/intel/templates/install.rb +11 -2
- data/lib/intel.rb +26 -6
- data/lib/intel/engine.rb +1 -0
- data/lib/intel/search.rb +14 -0
- data/lib/intel/track.rb +1 -1
- data/lib/intel/version.rb +1 -1
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6afd8d515e1b008debd378f41f709f6a9e59b20
|
4
|
+
data.tar.gz: e514f5ffc47f3e45bdb1ab72b68117d50201e981
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba504e4d707d5bdd3aa542443bb3769418bf4e13aa5d1ddef9cc855f1aeca7e99faea8034c66c12a7f0ba4dd42d57d4aa3696a890408d3d71048c4af3ca2a151
|
7
|
+
data.tar.gz: ba7dae63eaa56c1380733f31acde069c9e1a84be927af85dd1109abb44b358c7c1e2aeccbb2823d1c8d6062e0c63fd8dbea92ffdfbce5c5c73efb0231cffc34c
|
data/README.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# Intel
|
2
2
|
|
3
|
-
Search analytics made easy
|
3
|
+
:monkey_face: Search analytics made easy
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
- view searches
|
5
|
+
[See it in action](http://intel-demo.herokuapp.com/)
|
6
|
+
|
7
|
+
- view searches in real-time
|
8
|
+
- track conversions week over week
|
9
|
+
- monitor the performance of top searches
|
10
|
+
|
11
|
+
:cupid: An amazing companion to [Searchkick](https://github.com/ankane/searchkick)
|
12
|
+
|
13
|
+
Works with Rails 3.1+ and any search engine, including Elasticsearch, Sphinx, and Solr
|
8
14
|
|
9
15
|
## Get Started
|
10
16
|
|
@@ -14,43 +20,136 @@ Add this line to your application’s Gemfile:
|
|
14
20
|
gem "intel"
|
15
21
|
```
|
16
22
|
|
17
|
-
|
23
|
+
And run the generator. This creates a migration to store searches.
|
18
24
|
|
19
25
|
```sh
|
20
26
|
rails generate intel:install
|
21
27
|
rake db:migrate
|
22
28
|
```
|
23
29
|
|
24
|
-
|
30
|
+
Next, add the dashboard to your `config/routes.rb`.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
mount Intel::Engine, at: "admin/intel"
|
34
|
+
```
|
35
|
+
|
36
|
+
Be sure to protect the endpoint in production - see the [Authentication](#authentication) section for ways to do this.
|
37
|
+
|
38
|
+
### Track Searches
|
39
|
+
|
40
|
+
Track searches by creating a record in the database.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
Intel::Search.create(
|
44
|
+
search_type: "Item", # typically the model name
|
45
|
+
query: "apple",
|
46
|
+
results_count: 12
|
47
|
+
)
|
48
|
+
```
|
49
|
+
|
50
|
+
With [Searchkick](https://github.com/ankane/searchkick), you can use the `track` option to do this automatically.
|
25
51
|
|
26
52
|
```ruby
|
27
53
|
Item.search "apple", track: true
|
28
54
|
```
|
29
55
|
|
30
|
-
|
56
|
+
If you want to track more attributes, add them to the `intel_searches` table. Then, pass the values to the `track` option.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Item.search "apple", track: {user_id: 1, source: "web"}
|
60
|
+
```
|
61
|
+
|
62
|
+
It’s that easy.
|
63
|
+
|
64
|
+
### Track Conversions
|
65
|
+
|
66
|
+
Tracking conversions is super important.
|
67
|
+
|
68
|
+
First, define your conversion metric. This is specific to your application.
|
69
|
+
|
70
|
+
Next, when a user searches, keep track of the search id.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
@items = Item.search "apple", track: true
|
74
|
+
@items.search.id # returns search id
|
75
|
+
```
|
76
|
+
|
77
|
+
When a user converts, mark it.
|
31
78
|
|
32
79
|
```ruby
|
33
|
-
|
80
|
+
search = Intel::Search.find params[:search_id]
|
81
|
+
search.converted_at = Time.now
|
82
|
+
search.save
|
34
83
|
```
|
35
84
|
|
36
|
-
|
85
|
+
Better yet, record the result that converted.
|
37
86
|
|
38
|
-
|
87
|
+
```ruby
|
88
|
+
item = Item.find params[:item_id]
|
89
|
+
search.convertable = item
|
90
|
+
search.save
|
91
|
+
```
|
92
|
+
|
93
|
+
The item will appear in the live stream. Add the `intel_name` method to your model to change what is displayed.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class Item < ActiveRecord::Base
|
97
|
+
def intel_name
|
98
|
+
title # use the title method
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Authentication
|
104
|
+
|
105
|
+
Don’t forget to protect the dashboard in production.
|
106
|
+
|
107
|
+
#### Basic Authentication
|
108
|
+
|
109
|
+
Set the following variables in your environment or an initializer.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
ENV["INTEL_USERNAME"] = "andrew"
|
113
|
+
ENV["INTEL_PASSWORD"] = "secret"
|
114
|
+
```
|
39
115
|
|
40
|
-
|
116
|
+
#### Devise
|
41
117
|
|
42
118
|
```ruby
|
43
|
-
|
119
|
+
authenticate :user, lambda{|user| user.admin? } do
|
120
|
+
mount Intel::Engine, at: "admin/intel"
|
121
|
+
end
|
44
122
|
```
|
45
123
|
|
46
|
-
|
124
|
+
### Customize
|
47
125
|
|
48
|
-
|
126
|
+
#### Time Zone
|
127
|
+
|
128
|
+
To change the time zone, create an initializer `config/initializers/intel.rb` with:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
Intel.time_zone = "Pacific Time (US & Canada)" # defaults to Time.zone
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Top Searches
|
135
|
+
|
136
|
+
Change the number of top searches shown with:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
Intel.top_searches = 500 # defaults to 100
|
140
|
+
```
|
141
|
+
|
142
|
+
## TODO
|
143
|
+
|
144
|
+
- customize views
|
145
|
+
- group similar queries
|
146
|
+
- track pagination, facets, sorting, etc
|
49
147
|
|
50
148
|
## Contributing
|
51
149
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
150
|
+
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
151
|
+
|
152
|
+
- [Report bugs](https://github.com/ankane/intel/issues)
|
153
|
+
- Fix bugs and [submit pull requests](https://github.com/ankane/intel/pulls)
|
154
|
+
- Write, clarify, or fix documentation
|
155
|
+
- Suggest or add new feature
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Intel
|
2
|
+
class SearchesController < ActionController::Base
|
3
|
+
layout "intel/application"
|
4
|
+
|
5
|
+
http_basic_authenticate_with name: ENV["INTEL_USERNAME"], password: ENV["INTEL_PASSWORD"] if ENV["INTEL_PASSWORD"]
|
6
|
+
|
7
|
+
before_filter :set_time_zone
|
8
|
+
before_filter :set_search_types
|
9
|
+
before_filter :set_search_type, only: [:index, :overview]
|
10
|
+
before_filter :set_time_range, only: [:index, :overview]
|
11
|
+
before_filter :set_searches, only: [:index, :overview]
|
12
|
+
|
13
|
+
def index
|
14
|
+
if params[:sort] == "conversion_rate"
|
15
|
+
@searches.sort_by!{|s| [s["conversion_rate"].to_f, s["query"]] }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def overview
|
20
|
+
relation = Intel::Search.where(search_type: params[:search_type])
|
21
|
+
@searches_by_week = relation.group_by_week(:created_at, Time.zone, @time_range).count
|
22
|
+
@conversions_by_week = relation.where("converted_at is not null").group_by_week(:created_at, Time.zone, @time_range).count
|
23
|
+
@top_searches = @searches.first(5)
|
24
|
+
@bad_conversion_rate = @searches.sort_by{|s| [s["conversion_rate"].to_f, s["query"]] }.first(5).select{|s| s["conversion_rate"] < 50 }
|
25
|
+
@conversion_rate_by_week = {}
|
26
|
+
@searches_by_week.each do |week, searches_count|
|
27
|
+
@conversion_rate_by_week[week] = searches_count > 0 ? (100.0 * @conversions_by_week[week] / searches_count).round : 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stream
|
32
|
+
end
|
33
|
+
|
34
|
+
# suspiciously similar to bootstrap 3
|
35
|
+
COLORS = %w[5bc0de d9534f 5cb85c f0ad4e]
|
36
|
+
|
37
|
+
def recent
|
38
|
+
@searches = Intel::Search.order("created_at desc").limit(10)
|
39
|
+
@color = {}
|
40
|
+
@search_types.each_with_index do |search_type, i|
|
41
|
+
@color[search_type] = COLORS[i % COLORS.size]
|
42
|
+
end
|
43
|
+
render layout: false
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def set_search_types
|
49
|
+
@search_types = Intel::Search.uniq.pluck(:search_type).sort
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_search_type
|
53
|
+
@search_type = params[:search_type].to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_time_zone
|
57
|
+
@time_zone = Intel.time_zone
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_time_range
|
61
|
+
@time_range = 8.weeks.ago.in_time_zone(@time_zone).beginning_of_week(:sunday)..Time.now
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_searches
|
65
|
+
@limit = params[:limit] || Intel.top_searches
|
66
|
+
@searches = Intel::Search.connection.select_all(Intel::Search.select("normalized_query, COUNT(*) as searches_count, COUNT(converted_at) as conversions_count, AVG(results_count) as avg_results_count").where(created_at: @time_range, search_type: @search_type).group("normalized_query").order("searches_count desc, normalized_query asc").limit(@limit).to_sql).to_a
|
67
|
+
@searches.each do |search|
|
68
|
+
search["conversion_rate"] = 100 * search["conversions_count"].to_i / search["searches_count"].to_f
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<h1>
|
2
|
+
<%= @search_type %> Searches
|
3
|
+
<small style="color: #999; font-weight: normal;">Top <%= @limit %></small>
|
4
|
+
</h1>
|
5
|
+
|
6
|
+
<table>
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th>Query</th>
|
10
|
+
<th class="num" style="width: 20%;"><%= link_to "Searches", params.except(:sort), class: ("active" if params[:sort] != "conversion_rate") %></th>
|
11
|
+
<th class="num" style="width: 20%;">Conversions</th>
|
12
|
+
<th class="num" style="width: 20%;"><%= link_to "%", params.merge(sort: "conversion_rate"), class: ("active" if params[:sort] == "conversion_rate") %></th>
|
13
|
+
<th class="num" style="width: 20%;">Avg Results</th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
<tbody>
|
17
|
+
<% @searches.each do |search| %>
|
18
|
+
<tr>
|
19
|
+
<td><%= search["normalized_query"] %></td>
|
20
|
+
<td class="num"><%= search["searches_count"] %></td>
|
21
|
+
<td class="num"><%= search["conversions_count"] %></td>
|
22
|
+
<td class="num"><%= number_to_percentage search["conversion_rate"].to_f, precision: 0 %></td>
|
23
|
+
<td class="num"><%= search["avg_results_count"].to_f.round %></td>
|
24
|
+
</tr>
|
25
|
+
<% end %>
|
26
|
+
</tbody>
|
27
|
+
</table>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<h1><%= @search_type %> Overview</h1>
|
2
|
+
|
3
|
+
<div class="grid">
|
4
|
+
<div class="col-1-2">
|
5
|
+
<table>
|
6
|
+
<thead>
|
7
|
+
<tr>
|
8
|
+
<th>Top Searches</th>
|
9
|
+
<th class="num"><%= link_to "View all", intel.searches_path(search_type: params[:search_type]) %></th>
|
10
|
+
</tr>
|
11
|
+
</thead>
|
12
|
+
<tbody>
|
13
|
+
<% @top_searches.each do |row| %>
|
14
|
+
<tr>
|
15
|
+
<td><%= row["normalized_query"] %></td>
|
16
|
+
<td class="num"><%= row["searches_count"] %></td>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
</div>
|
22
|
+
<div class="col-1-2">
|
23
|
+
<table>
|
24
|
+
<thead>
|
25
|
+
<tr>
|
26
|
+
<th>Low Conversions</th>
|
27
|
+
<th class="num"><%= link_to "View all", intel.searches_path(search_type: params[:search_type], sort: "conversion_rate") %></th>
|
28
|
+
</tr>
|
29
|
+
</thead>
|
30
|
+
<tbody>
|
31
|
+
<% @bad_conversion_rate.each do |row| %>
|
32
|
+
<tr>
|
33
|
+
<td><%= row["normalized_query"] %></td>
|
34
|
+
<td class="num"><%= number_to_percentage row["conversion_rate"], precision: 0 %></td>
|
35
|
+
</tr>
|
36
|
+
<% end %>
|
37
|
+
</tbody>
|
38
|
+
</table>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<h2>Conversion Rate</h2>
|
43
|
+
|
44
|
+
<%= line_chart @conversion_rate_by_week %>
|
45
|
+
|
46
|
+
<h2>Volume</h2>
|
47
|
+
|
48
|
+
<%= line_chart [{name: "Searches", data: @searches_by_week}, {name: "Conversions", data: @conversions_by_week}] %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% @searches.each do |search| %>
|
2
|
+
<tr>
|
3
|
+
<td style="width: 15%;">
|
4
|
+
<%= link_to search.search_type, intel.overview_searches_path(search_type: search.search_type), class: "type-link", style: "background-color: ##{@color[search.search_type]};" %>
|
5
|
+
</td>
|
6
|
+
<td><%= search.query %></td>
|
7
|
+
<td style="width: 35%;">
|
8
|
+
<% if search.converted_at %>
|
9
|
+
<span style="color: #5cb85c;">
|
10
|
+
<span style="font-weight: bold;">✓</span>
|
11
|
+
<%= search.convertable ? (search.convertable.respond_to?(:intel_name) ? search.convertable.intel_name : "#{search.convertable_type} #{search.convertable_id}") : "Converted" %>
|
12
|
+
</span>
|
13
|
+
<% end %>
|
14
|
+
</td>
|
15
|
+
<td style="width: 20%;" class="num">
|
16
|
+
<%= time_ago_in_words search.created_at %> ago
|
17
|
+
<div style="color: #999;"><%= pluralize search.results_count, "result" %></div>
|
18
|
+
</td>
|
19
|
+
</tr>
|
20
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<h1>Live Stream</h1>
|
2
|
+
|
3
|
+
<table id="stream"></table>
|
4
|
+
|
5
|
+
<script>
|
6
|
+
function fetchRecentSearches() {
|
7
|
+
$("#stream").load("<%= intel.searches_recent_path %>");
|
8
|
+
setTimeout(fetchRecentSearches, 5 * 1000);
|
9
|
+
}
|
10
|
+
$( function() {
|
11
|
+
fetchRecentSearches();
|
12
|
+
});
|
13
|
+
</script>
|
@@ -0,0 +1,291 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Intel</title>
|
5
|
+
|
6
|
+
<meta charset="utf-8" />
|
7
|
+
|
8
|
+
<style>
|
9
|
+
body {
|
10
|
+
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
11
|
+
margin: 0;
|
12
|
+
padding: 20px;
|
13
|
+
font-size: 14px;
|
14
|
+
line-height: 1.4;
|
15
|
+
color: #333;
|
16
|
+
}
|
17
|
+
|
18
|
+
a, a:visited, a:active {
|
19
|
+
color: #428bca;
|
20
|
+
text-decoration: none;
|
21
|
+
}
|
22
|
+
|
23
|
+
a:hover {
|
24
|
+
text-decoration: underline;
|
25
|
+
}
|
26
|
+
|
27
|
+
table {
|
28
|
+
width: 100%;
|
29
|
+
border-collapse: collapse;
|
30
|
+
border-spacing: 0;
|
31
|
+
margin-bottom: 20px;
|
32
|
+
}
|
33
|
+
|
34
|
+
th {
|
35
|
+
text-align: left;
|
36
|
+
border-bottom: solid 2px #ddd;
|
37
|
+
}
|
38
|
+
|
39
|
+
h1 {
|
40
|
+
font-size: 20px;
|
41
|
+
}
|
42
|
+
|
43
|
+
h2 {
|
44
|
+
font-size: 16px;
|
45
|
+
}
|
46
|
+
|
47
|
+
ul {
|
48
|
+
list-style-type: none;
|
49
|
+
padding: 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
table td, table th {
|
53
|
+
padding: 8px;
|
54
|
+
}
|
55
|
+
|
56
|
+
td {
|
57
|
+
border-top: solid 1px #ddd;
|
58
|
+
}
|
59
|
+
|
60
|
+
#brand {
|
61
|
+
font-size: 18px;
|
62
|
+
line-height: 20px;
|
63
|
+
font-weight: bold;
|
64
|
+
color: #999;
|
65
|
+
}
|
66
|
+
|
67
|
+
a.active {
|
68
|
+
color: #5cb85c;
|
69
|
+
}
|
70
|
+
|
71
|
+
a.type-link {
|
72
|
+
color: #fff;
|
73
|
+
padding: 8px;
|
74
|
+
border-radius: 4px;
|
75
|
+
}
|
76
|
+
|
77
|
+
a.type-link:hover {
|
78
|
+
text-decoration: none;
|
79
|
+
}
|
80
|
+
|
81
|
+
#header {
|
82
|
+
border-bottom: solid 1px #ddd;
|
83
|
+
padding-bottom: 20px;
|
84
|
+
}
|
85
|
+
|
86
|
+
.nav, .nav li {
|
87
|
+
display: inline;
|
88
|
+
}
|
89
|
+
|
90
|
+
.nav li {
|
91
|
+
margin-right: 20px;
|
92
|
+
}
|
93
|
+
|
94
|
+
#stream {
|
95
|
+
border-bottom: solid 1px #ddd;
|
96
|
+
}
|
97
|
+
|
98
|
+
.num {
|
99
|
+
text-align: right;
|
100
|
+
}
|
101
|
+
|
102
|
+
.container {
|
103
|
+
max-width: 800px;
|
104
|
+
margin-left: auto;
|
105
|
+
margin-right: auto;
|
106
|
+
}
|
107
|
+
|
108
|
+
/*
|
109
|
+
Simple Grid
|
110
|
+
Learn More - http://dallasbass.com/simple-grid-a-lightweight-responsive-css-grid/
|
111
|
+
Project Page - http://thisisdallas.github.com/Simple-Grid/
|
112
|
+
Author - Dallas Bass
|
113
|
+
Site - dallasbass.com
|
114
|
+
*/
|
115
|
+
|
116
|
+
*, *:after, *:before {
|
117
|
+
-webkit-box-sizing: border-box;
|
118
|
+
-moz-box-sizing: border-box;
|
119
|
+
box-sizing: border-box;
|
120
|
+
}
|
121
|
+
|
122
|
+
body {
|
123
|
+
margin: 0px;
|
124
|
+
}
|
125
|
+
|
126
|
+
[class*='col-'] {
|
127
|
+
float: left;
|
128
|
+
padding-right: 20px;
|
129
|
+
}
|
130
|
+
|
131
|
+
[class*='col-']:last-of-type {
|
132
|
+
padding-right: 0px;
|
133
|
+
}
|
134
|
+
|
135
|
+
.grid {
|
136
|
+
width: 100%;
|
137
|
+
|
138
|
+
margin: 0 auto;
|
139
|
+
overflow: hidden;
|
140
|
+
}
|
141
|
+
|
142
|
+
.grid:after {
|
143
|
+
content: "";
|
144
|
+
display: table;
|
145
|
+
clear: both;
|
146
|
+
}
|
147
|
+
|
148
|
+
.grid-pad {
|
149
|
+
padding: 20px 0 0px 20px;
|
150
|
+
}
|
151
|
+
|
152
|
+
.grid-pad > [class*='col-']:last-of-type {
|
153
|
+
padding-right: 20px;
|
154
|
+
}
|
155
|
+
|
156
|
+
.push-right {
|
157
|
+
float: right;
|
158
|
+
}
|
159
|
+
|
160
|
+
/* Content Columns */
|
161
|
+
|
162
|
+
.col-1-1 {
|
163
|
+
width: 100%;
|
164
|
+
}
|
165
|
+
.col-2-3, .col-8-12 {
|
166
|
+
width: 66.66%;
|
167
|
+
}
|
168
|
+
|
169
|
+
.col-1-2, .col-6-12 {
|
170
|
+
width: 50%;
|
171
|
+
}
|
172
|
+
|
173
|
+
.col-1-3, .col-4-12 {
|
174
|
+
width: 33.33%;
|
175
|
+
}
|
176
|
+
|
177
|
+
.col-1-4, .col-3-12 {
|
178
|
+
width: 25%;
|
179
|
+
}
|
180
|
+
|
181
|
+
.col-1-5 {
|
182
|
+
width: 20%;
|
183
|
+
}
|
184
|
+
|
185
|
+
.col-1-6, .col-2-12 {
|
186
|
+
width: 16.667%;
|
187
|
+
}
|
188
|
+
|
189
|
+
.col-1-7 {
|
190
|
+
width: 14.28%;
|
191
|
+
}
|
192
|
+
|
193
|
+
.col-1-8 {
|
194
|
+
width: 12.5%;
|
195
|
+
}
|
196
|
+
|
197
|
+
.col-1-9 {
|
198
|
+
width: 11.1%;
|
199
|
+
}
|
200
|
+
|
201
|
+
.col-1-10 {
|
202
|
+
width: 10%;
|
203
|
+
}
|
204
|
+
|
205
|
+
.col-1-11 {
|
206
|
+
width: 9.09%;
|
207
|
+
}
|
208
|
+
|
209
|
+
.col-1-12 {
|
210
|
+
width: 8.33%
|
211
|
+
}
|
212
|
+
|
213
|
+
/* Layout Columns */
|
214
|
+
|
215
|
+
.col-11-12 {
|
216
|
+
width: 91.66%
|
217
|
+
}
|
218
|
+
|
219
|
+
.col-10-12 {
|
220
|
+
width: 83.333%;
|
221
|
+
}
|
222
|
+
|
223
|
+
.col-9-12 {
|
224
|
+
width: 75%;
|
225
|
+
}
|
226
|
+
|
227
|
+
.col-5-12 {
|
228
|
+
width: 41.66%;
|
229
|
+
}
|
230
|
+
|
231
|
+
.col-7-12 {
|
232
|
+
width: 58.33%
|
233
|
+
}
|
234
|
+
|
235
|
+
@media handheld, only screen and (max-width: 767px) {
|
236
|
+
|
237
|
+
|
238
|
+
.grid {
|
239
|
+
width: 100%;
|
240
|
+
min-width: 0;
|
241
|
+
margin-left: 0px;
|
242
|
+
margin-right: 0px;
|
243
|
+
padding-left: 0px;
|
244
|
+
padding-right: 0px;
|
245
|
+
}
|
246
|
+
|
247
|
+
[class*='col-'] {
|
248
|
+
width: auto;
|
249
|
+
float: none;
|
250
|
+
margin-left: 0px;
|
251
|
+
margin-right: 0px;
|
252
|
+
margin-top: 10px;
|
253
|
+
margin-bottom: 10px;
|
254
|
+
padding-right: 0px;
|
255
|
+
padding-left: 0px;
|
256
|
+
}
|
257
|
+
}
|
258
|
+
</style>
|
259
|
+
|
260
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
261
|
+
<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>
|
262
|
+
</head>
|
263
|
+
<body>
|
264
|
+
<div class="container">
|
265
|
+
<% if !@skip_header %>
|
266
|
+
<div id="header">
|
267
|
+
<div class="grid">
|
268
|
+
<div class="col-1-2">
|
269
|
+
<ul class="nav">
|
270
|
+
<li id="brand">Intel</li>
|
271
|
+
<li><%= link_to "Live Stream", intel.root_path, class: ("active" if !@search_type) %></li>
|
272
|
+
<% @search_types.each do |search_type| %>
|
273
|
+
<li><%= link_to search_type, intel.overview_searches_path(search_type: search_type), class: ("active" if @search_type == search_type) %></li>
|
274
|
+
<% end %>
|
275
|
+
</ul>
|
276
|
+
</div>
|
277
|
+
|
278
|
+
<div class="col-1-2" style="text-align: right;">
|
279
|
+
<% if %w[overview index].include?(params[:action]) and @time_range %>
|
280
|
+
<%= @time_range.first.strftime("%b %-e, %Y") %> to <%= @time_range.last.strftime("%b %-e, %Y") %>
|
281
|
+
<span style="color: #999;"><%= @time_zone.name.sub(" (US & Canada)", "") %></span>
|
282
|
+
<% end %>
|
283
|
+
</div>
|
284
|
+
</div>
|
285
|
+
</div>
|
286
|
+
<% end %>
|
287
|
+
|
288
|
+
<%= yield %>
|
289
|
+
</div>
|
290
|
+
</body>
|
291
|
+
</html>
|
data/config/routes.rb
ADDED
data/intel.gemspec
CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "
|
21
|
+
spec.add_dependency "chartkick"
|
22
|
+
spec.add_dependency "groupdate"
|
22
23
|
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
25
|
spec.add_development_dependency "rake"
|
@@ -1,10 +1,19 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
2
|
def change
|
3
|
-
create_table :
|
4
|
-
t.string :
|
3
|
+
create_table :intel_searches do |t|
|
4
|
+
t.string :search_type
|
5
5
|
t.string :query
|
6
|
+
t.string :normalized_query
|
6
7
|
t.integer :results_count
|
7
8
|
t.timestamp :created_at
|
9
|
+
t.integer :convertable_id
|
10
|
+
t.string :convertable_type
|
11
|
+
t.timestamp :converted_at
|
8
12
|
end
|
13
|
+
|
14
|
+
add_index :intel_searches, [:created_at]
|
15
|
+
add_index :intel_searches, [:search_type, :created_at]
|
16
|
+
add_index :intel_searches, [:search_type, :normalized_query, :created_at], name: "index_intel_searches_on_search_type_and_normalized_query_and_cr" # autogenerated name is too long
|
17
|
+
add_index :intel_searches, [:convertable_id, :convertable_type]
|
9
18
|
end
|
10
19
|
end
|
data/lib/intel.rb
CHANGED
@@ -1,14 +1,34 @@
|
|
1
|
-
require "searchkick"
|
2
1
|
require "intel/search"
|
3
2
|
require "intel/track"
|
4
3
|
require "intel/engine"
|
5
4
|
require "intel/version"
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
include Intel::Track
|
6
|
+
require "chartkick"
|
7
|
+
require "groupdate"
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
module Intel
|
10
|
+
# time zone
|
11
|
+
mattr_reader :time_zone
|
12
|
+
def self.time_zone=(time_zone)
|
13
|
+
@@time_zone = time_zone.is_a?(String) ? ActiveSupport::TimeZone.new(time_zone) : time_zone
|
14
|
+
end
|
15
|
+
|
16
|
+
# top searches
|
17
|
+
mattr_accessor :top_searches
|
18
|
+
self.top_searches = 100
|
19
|
+
end
|
20
|
+
|
21
|
+
if defined?(Searchkick)
|
22
|
+
module Searchkick
|
23
|
+
module Search
|
24
|
+
include Intel::Track
|
25
|
+
|
26
|
+
alias_method :search_without_track, :search
|
27
|
+
alias_method :search, :search_with_track
|
28
|
+
end
|
29
|
+
|
30
|
+
class Results
|
31
|
+
attr_accessor :search
|
32
|
+
end
|
13
33
|
end
|
14
34
|
end
|
data/lib/intel/engine.rb
CHANGED
data/lib/intel/search.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
module Intel
|
2
2
|
class Search < ActiveRecord::Base
|
3
|
+
belongs_to :convertable, polymorphic: true
|
4
|
+
|
5
|
+
before_save :set_normalized_query
|
6
|
+
|
7
|
+
def converted?
|
8
|
+
converted_at.present?
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def set_normalized_query
|
14
|
+
self.normalized_query = query.downcase if query
|
15
|
+
end
|
16
|
+
|
3
17
|
end
|
4
18
|
end
|
data/lib/intel/track.rb
CHANGED
@@ -6,7 +6,7 @@ module Intel
|
|
6
6
|
|
7
7
|
if options[:track]
|
8
8
|
attributes = options[:track] == true ? {} : options[:track]
|
9
|
-
Intel::Search.create({
|
9
|
+
results.search = Intel::Search.create({search_type: self.name, query: term, results_count: results.total_count}.merge(attributes))
|
10
10
|
end
|
11
11
|
|
12
12
|
results
|
data/lib/intel/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: intel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: chartkick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: groupdate
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - '>='
|
@@ -64,6 +78,13 @@ files:
|
|
64
78
|
- LICENSE.txt
|
65
79
|
- README.md
|
66
80
|
- Rakefile
|
81
|
+
- app/controllers/intel/searches_controller.rb
|
82
|
+
- app/views/intel/searches/index.html.erb
|
83
|
+
- app/views/intel/searches/overview.html.erb
|
84
|
+
- app/views/intel/searches/recent.html.erb
|
85
|
+
- app/views/intel/searches/stream.html.erb
|
86
|
+
- app/views/layouts/intel/application.html.erb
|
87
|
+
- config/routes.rb
|
67
88
|
- intel.gemspec
|
68
89
|
- lib/generators/intel/install_generator.rb
|
69
90
|
- lib/generators/intel/templates/install.rb
|