intel 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|