searchjoy 0.5.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a837b7470769cd1d7589023ab3f1ffed51b683ea67016d3f642aa26b9edcab3e
4
- data.tar.gz: 54239d751a05741b0b8fbfcedbc6d130af6a6153499ada75e282bfad7acec716
3
+ metadata.gz: 807861a09661dcbcf5a2f4c580e189e3df4d7f67e3a1dbb863408ce7fc1091c3
4
+ data.tar.gz: b901df59229c2a6daa3781a44b66b89d87d171aeede2acbd17f9ef135e78c7fd
5
5
  SHA512:
6
- metadata.gz: 8f35d980883ded923995f9340867b1685bcb710e66976ce8ce72e18033de94155c49ff1b53b8c4ba2fdb3d4c1f365bd0db8ee8dd0e70de39730e9f8f4124fb36
7
- data.tar.gz: 191ee153cd9941773f2c388b38816513e1319ec7f29f5a6d59ad8d5182e310080d7260a9ebfecc7e4dda8801f61dbaab3576426912848787a713c60ef8c92782
6
+ metadata.gz: 4c7d96778f812145a132f8f756ac2ca1bb88429758db180402f47b0f56d68b9dfe63e461668b28a51162018f7f97f49c6f94100e3430a79bf54056cd2b4fcdbb
7
+ data.tar.gz: 744e8fff8679dfc197d4439c7cda43cc21b23f38e12aec883a1b095481ae4f3db597b3f26bc2c409c51c07928a4f68a4a5114b387905b5c0c6f119611dbac7b1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.0.0 (2022-05-10)
2
+
3
+ - Added support for multiple conversions per search
4
+ - Added experimental support for Propshaft
5
+ - Dropped support for Ruby < 2.6 and Rails < 5.2
6
+
1
7
  ## 0.5.1 (2020-09-07)
2
8
 
3
9
  - Use `datetime` type in migration
@@ -57,3 +63,44 @@ Breaking
57
63
  ## 0.1.0 (2016-08-23)
58
64
 
59
65
  - Fixed Searchkick integration for `execute: false`
66
+
67
+ ## 0.0.10 (2016-04-20)
68
+
69
+ - Fixed error when block passed to `search`
70
+
71
+ ## 0.0.9 (2016-03-12)
72
+
73
+ - Fixed error with routes
74
+
75
+ ## 0.0.8 (2015-12-12)
76
+
77
+ - Added `user_id` to searches
78
+
79
+ ## 0.0.7 (2014-08-18)
80
+
81
+ - Fixed error with Searchkick 0.8
82
+
83
+ ## 0.0.6 (2013-12-19)
84
+
85
+ - Fixed error with Rails 3
86
+
87
+ ## 0.0.5 (2013-11-01)
88
+
89
+ - Renamed to Searchjoy
90
+
91
+ ## 0.0.4 (2013-10-31)
92
+
93
+ - Renamed `converted` to `convert`
94
+
95
+ ## 0.0.3 (2013-10-31)
96
+
97
+ - Added `conversion_name` option
98
+ - Added `converted` method
99
+
100
+ ## 0.0.2 (2013-10-31)
101
+
102
+ - Added dashboard
103
+
104
+ ## 0.0.1 (2013-10-24)
105
+
106
+ - First release
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2020 Andrew Kane
1
+ Copyright (c) 2013-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,7 +4,7 @@ Search analytics made easy
4
4
 
5
5
  [See it in action](https://searchjoy.dokkuapp.com/)
6
6
 
7
- [![Screenshot](https://searchjoy.dokkuapp.com/assets/searchjoy-a9d4c01356a822bc1dba1eecf7ac76387de009fc9902eb9a880057069212be80.png)](https://searchjoy.dokkuapp.com/)
7
+ [![Screenshot](https://searchjoy.dokkuapp.com/assets/searchjoy-7be12d922ca8b31b7d7440e618b0c666698a4b15752653a0c5c45e3dd2737142.png)](https://searchjoy.dokkuapp.com/)
8
8
 
9
9
  - view searches in real-time
10
10
  - track conversions week over week
@@ -14,7 +14,7 @@ Works with any search platform, including Elasticsearch, Sphinx, and Solr
14
14
 
15
15
  :cupid: An amazing companion to [Searchkick](https://github.com/ankane/searchkick)
16
16
 
17
- [![Build Status](https://travis-ci.org/ankane/searchjoy.svg?branch=master)](https://travis-ci.org/ankane/searchjoy)
17
+ [![Build Status](https://github.com/ankane/searchjoy/workflows/build/badge.svg?branch=master)](https://github.com/ankane/searchjoy/actions)
18
18
 
19
19
  ## Installation
20
20
 
@@ -70,8 +70,6 @@ Then, pass the values to the `track` option.
70
70
  Item.search("apple", track: {user_id: 1, source: "web"})
71
71
  ```
72
72
 
73
- It’s that easy.
74
-
75
73
  ## Track Conversions
76
74
 
77
75
  First, choose a conversion metric. At Instacart, an item added to the cart from the search results page counts as a conversion.
@@ -121,12 +119,18 @@ ENV["SEARCHJOY_PASSWORD"] = "secret"
121
119
 
122
120
  ## Data Retention
123
121
 
124
- Delete older data with:
122
+ Data should only be retained for as long as it’s needed. Delete older data with:
125
123
 
126
124
  ```ruby
127
125
  Searchjoy::Search.where("created_at < ?", 1.year.ago).in_batches.delete_all
128
126
  ```
129
127
 
128
+ You can use [Rollup](https://github.com/ankane/rollup) to aggregate important data before you do.
129
+
130
+ ```ruby
131
+ Searchjoy::Search.rollup("Searches")
132
+ ```
133
+
130
134
  Delete data for a specific user with:
131
135
 
132
136
  ```ruby
@@ -167,6 +171,46 @@ Show the conversion name in the live stream
167
171
  Searchjoy.conversion_name = ->(model) { model.name }
168
172
  ```
169
173
 
174
+ ## Upgrading
175
+
176
+ ### 1.0
177
+
178
+ Searchjoy now supports multiple conversions per search :tada:
179
+
180
+ Before updating the gem, create a migration with:
181
+
182
+ ```ruby
183
+ create_table :searchjoy_conversions do |t|
184
+ t.references :search
185
+ t.references :convertable, polymorphic: true, index: {name: "index_searchjoy_conversions_on_convertable"}
186
+ t.datetime :created_at
187
+ end
188
+ ```
189
+
190
+ Deploy and run the migration, then update the gem.
191
+
192
+ You can optionally backfill the conversions table
193
+
194
+ ```ruby
195
+ Searchjoy.backfill_conversions
196
+ ```
197
+
198
+ And optionally remove `convertable` from searches
199
+
200
+ ```ruby
201
+ remove_reference :searchjoy_searches, :convertable, polymorphic: true
202
+ ```
203
+
204
+ You can stay with single conversions (and skip all the previous steps) by creating an initializer with:
205
+
206
+ ```ruby
207
+ Searchjoy.multiple_conversions = false
208
+ ```
209
+
210
+ ## History
211
+
212
+ View the [changelog](https://github.com/ankane/searchjoy/blob/master/CHANGELOG.md)
213
+
170
214
  ## Contributing
171
215
 
172
216
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
@@ -23,18 +23,18 @@ module Searchjoy
23
23
  duration = @time_range.last - @time_range.first
24
24
  period =
25
25
  if duration < 3.days # shows 48-72 data points (ends at current time)
26
- "hour"
26
+ :hour
27
27
  elsif duration < 60.days
28
- "day"
28
+ :day
29
29
  elsif duration < 60.weeks # to make it easy to compare to same time last year
30
- "week"
30
+ :week
31
31
  else
32
- "month"
32
+ :month
33
33
  end
34
34
 
35
35
  relation = Searchjoy::Search.where(search_type: params[:search_type])
36
36
  @searches_by_week = relation.group_by_period(period, :created_at, time_zone: @time_zone, range: @time_range).count
37
- @conversions_by_week = relation.where("converted_at is not null").group_by_period(period, :created_at, time_zone: @time_zone, range: @time_range).count
37
+ @conversions_by_week = relation.where.not(converted_at: nil).group_by_period(period, :created_at, time_zone: @time_zone, range: @time_range).count
38
38
  @top_searches = @searches.first(5)
39
39
  @bad_conversion_rate = @searches.sort_by { |s| [s["conversion_rate"].to_f, s["query"]] }.first(5).select { |s| s["conversion_rate"] < 50 }
40
40
  @conversion_rate_by_week = {}
@@ -47,7 +47,12 @@ module Searchjoy
47
47
  end
48
48
 
49
49
  def recent
50
- @searches = Searchjoy::Search.includes(:convertable).order("created_at desc").limit(50)
50
+ @searches = Searchjoy::Search.order(created_at: :desc).limit(50)
51
+ if Searchjoy.multiple_conversions
52
+ @searches.includes!(conversions: :convertable)
53
+ else
54
+ @searches.includes!(:convertable)
55
+ end
51
56
  render layout: false
52
57
  end
53
58
 
@@ -81,7 +86,14 @@ module Searchjoy
81
86
 
82
87
  def set_searches
83
88
  @limit = params[:limit] || Searchjoy.top_searches
84
- @searches = Searchjoy::Search.connection.select_all(Searchjoy::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
89
+ relation = Searchjoy::Search
90
+ .select("normalized_query, COUNT(*) as searches_count, COUNT(converted_at) as conversions_count, AVG(results_count) as avg_results_count")
91
+ .where(created_at: @time_range, search_type: @search_type)
92
+ .group(:normalized_query)
93
+ .order(searches_count: :desc, normalized_query: :asc)
94
+ .limit(@limit)
95
+ # get array of hashes for performance
96
+ @searches = Searchjoy::Search.connection.select_all(relation.to_sql).to_a
85
97
  @searches.each do |search|
86
98
  search["conversion_rate"] = 100 * search["conversions_count"].to_i / search["searches_count"].to_f
87
99
  end
@@ -0,0 +1,8 @@
1
+ module Searchjoy
2
+ class Conversion < ActiveRecord::Base
3
+ self.table_name = "searchjoy_conversions"
4
+
5
+ belongs_to :search
6
+ belongs_to :convertable, polymorphic: true, optional: true
7
+ end
8
+ end
@@ -4,14 +4,34 @@ module Searchjoy
4
4
 
5
5
  belongs_to :convertable, polymorphic: true, optional: true
6
6
  belongs_to :user, optional: true
7
+ has_many :conversions
7
8
 
8
9
  before_save :set_normalized_query
9
10
 
10
11
  def convert(convertable = nil)
11
- unless converted?
12
- self.converted_at = Time.now
13
- self.convertable = convertable
14
- save(validate: false)
12
+ return unless Searchjoy.multiple_conversions || !converted?
13
+
14
+ # use transaction to keep consistent
15
+ self.class.transaction do
16
+ # make time consistent
17
+ now = Time.now
18
+
19
+ if Searchjoy.multiple_conversions
20
+ conversion =
21
+ conversions.create!(
22
+ convertable: convertable,
23
+ created_at: now
24
+ )
25
+ end
26
+
27
+ unless converted?
28
+ self.converted_at = now
29
+ # check id instead of association
30
+ self.convertable = convertable if respond_to?(:convertable_id=)
31
+ save(validate: false)
32
+ end
33
+
34
+ conversion
15
35
  end
16
36
  end
17
37
 
@@ -288,7 +288,11 @@
288
288
  }
289
289
  </style>
290
290
 
291
- <%= javascript_include_tag "searchjoy/application" %>
291
+ <% if defined?(Propshaft::Railtie) %>
292
+ <%= javascript_include_tag "chartkick", "Chart.bundle", "searchjoy/litepicker", "searchjoy/application" %>
293
+ <% else %>
294
+ <%= javascript_include_tag "searchjoy/application" %>
295
+ <% end %>
292
296
  </head>
293
297
  <body>
294
298
  <div class="container">
@@ -15,7 +15,8 @@
15
15
  <td style="width: 35%; color: #5cb85c;">
16
16
  <% if search.converted_at %>
17
17
  <strong>✓</strong>
18
- <%= search.convertable ? (Searchjoy.conversion_name ? Searchjoy.conversion_name.call(search.convertable) : "#{search.convertable_type} #{search.convertable_id}") : "Converted" %>
18
+ <% convertable = Searchjoy.multiple_conversions ? search.conversions.min_by(&:created_at)&.convertable : search.convertable %>
19
+ <%= convertable ? (Searchjoy.conversion_name ? Searchjoy.conversion_name.call(convertable) : "#{convertable.model_name.name} #{convertable.id}") : "Converted" %>
19
20
  <% end %>
20
21
  </td>
21
22
  <td style="width: 20%;" class="num">
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  function fetchRecentSearches() {
19
- load(document.getElementById("stream"), "<%= searches_recent_path %>")
19
+ load(document.getElementById("stream"), <%= raw json_escape(searches_recent_path.to_json) %>)
20
20
  setTimeout(fetchRecentSearches, 5 * 1000);
21
21
  }
22
22
  fetchRecentSearches();
@@ -7,12 +7,17 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
7
7
  t.string :normalized_query
8
8
  t.integer :results_count
9
9
  t.datetime :created_at
10
- t.references :convertable, polymorphic: true, index: {name: "index_searchjoy_searches_on_convertable"}
11
10
  t.datetime :converted_at
12
11
  end
13
12
 
14
13
  add_index :searchjoy_searches, [:created_at]
15
14
  add_index :searchjoy_searches, [:search_type, :created_at]
16
15
  add_index :searchjoy_searches, [:search_type, :normalized_query, :created_at], name: "index_searchjoy_searches_on_search_type_query" # autogenerated name is too long
16
+
17
+ create_table :searchjoy_conversions do |t|
18
+ t.references :search
19
+ t.references :convertable, polymorphic: true, index: {name: "index_searchjoy_conversions_on_convertable"}
20
+ t.datetime :created_at
21
+ end
17
22
  end
18
23
  end
@@ -1,3 +1,3 @@
1
1
  module Searchjoy
2
- VERSION = "0.5.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/searchjoy.rb CHANGED
@@ -23,11 +23,36 @@ module Searchjoy
23
23
  mattr_accessor :query_name
24
24
  mattr_accessor :query_url
25
25
 
26
+ # multiple conversions
27
+ mattr_accessor :multiple_conversions
28
+ self.multiple_conversions = true
29
+
26
30
  def self.attach_to_searchkick!
27
31
  Searchkick::Query.prepend(Searchjoy::Track::Query)
28
32
  Searchkick::MultiSearch.prepend(Searchjoy::Track::MultiSearch)
29
33
  Searchkick::Results.send(:attr_accessor, :search)
30
34
  end
35
+
36
+ def self.backfill_conversions
37
+ Searchjoy::Search.where.not(converted_at: nil).left_joins(:conversions).where(searchjoy_conversions: {id: nil}).find_in_batches do |searches|
38
+ conversions =
39
+ searches.map do |search|
40
+ {
41
+ search_id: search.id,
42
+ convertable_id: search.convertable_id,
43
+ convertable_type: search.convertable_type,
44
+ created_at: search.converted_at
45
+ }
46
+ end
47
+ if ActiveRecord::VERSION::MAJOR >= 6
48
+ Searchjoy::Conversion.insert_all(conversions)
49
+ else
50
+ Searchjoy::Conversion.transaction do
51
+ Searchjoy::Conversion.create!(conversions)
52
+ end
53
+ end
54
+ end
55
+ end
31
56
  end
32
57
 
33
58
  if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchjoy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-08 00:00:00.000000000 Z
11
+ date: 2022-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chartkick
@@ -44,86 +44,16 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '5'
47
+ version: '5.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '5'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rake
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: minitest
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: sqlite3
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: searchkick
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- description:
126
- email: andrew@chartkick.com
54
+ version: '5.2'
55
+ description:
56
+ email: andrew@ankane.org
127
57
  executables: []
128
58
  extensions: []
129
59
  extra_rdoc_files: []
@@ -134,6 +64,7 @@ files:
134
64
  - app/assets/javascripts/searchjoy/application.js
135
65
  - app/assets/javascripts/searchjoy/litepicker.js
136
66
  - app/controllers/searchjoy/searches_controller.rb
67
+ - app/models/searchjoy/conversion.rb
137
68
  - app/models/searchjoy/search.rb
138
69
  - app/views/layouts/searchjoy/application.html.erb
139
70
  - app/views/searchjoy/searches/index.html.erb
@@ -152,7 +83,7 @@ homepage: https://github.com/ankane/searchjoy
152
83
  licenses:
153
84
  - MIT
154
85
  metadata: {}
155
- post_install_message:
86
+ post_install_message:
156
87
  rdoc_options: []
157
88
  require_paths:
158
89
  - lib
@@ -160,15 +91,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
91
  requirements:
161
92
  - - ">="
162
93
  - !ruby/object:Gem::Version
163
- version: '2.4'
94
+ version: '2.6'
164
95
  required_rubygems_version: !ruby/object:Gem::Requirement
165
96
  requirements:
166
97
  - - ">="
167
98
  - !ruby/object:Gem::Version
168
99
  version: '0'
169
100
  requirements: []
170
- rubygems_version: 3.1.2
171
- signing_key:
101
+ rubygems_version: 3.3.7
102
+ signing_key:
172
103
  specification_version: 4
173
104
  summary: Search analytics made easy
174
105
  test_files: []