quick_search-core 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -6
  3. data/app/assets/javascripts/quick_search.js.erb +9 -0
  4. data/app/assets/javascripts/quick_search/appstats.js.erb +73 -0
  5. data/app/assets/javascripts/quick_search/appstats_best_bets.js.erb +905 -0
  6. data/app/assets/javascripts/quick_search/appstats_clicks_overview.js.erb +719 -0
  7. data/app/assets/javascripts/quick_search/appstats_general_statistics.js.erb +908 -0
  8. data/app/assets/javascripts/quick_search/appstats_sessions_details.js.erb +660 -0
  9. data/app/assets/javascripts/quick_search/appstats_sessions_overview.js.erb +481 -0
  10. data/app/assets/javascripts/quick_search/appstats_spelling_suggestions.js.erb +902 -0
  11. data/app/assets/javascripts/quick_search/appstats_top_searches.js.erb +633 -0
  12. data/app/assets/javascripts/quick_search/event_tracking.js.erb +22 -33
  13. data/app/assets/javascripts/quick_search/xhr_search.js +7 -3
  14. data/app/assets/stylesheets/quick_search/appstats.scss +148 -0
  15. data/app/assets/stylesheets/quick_search/quick_search.scss +0 -16
  16. data/app/controllers/concerns/quick_search/doi_trap.rb +1 -1
  17. data/app/controllers/quick_search/appstats_controller.rb +267 -144
  18. data/app/controllers/quick_search/logging_controller.rb +165 -0
  19. data/app/controllers/quick_search/search_controller.rb +1 -23
  20. data/app/controllers/quick_search/typeahead_controller.rb +48 -0
  21. data/app/models/event.rb +6 -0
  22. data/app/models/search.rb +7 -0
  23. data/app/models/session.rb +9 -0
  24. data/app/searchers/quick_search/best_bets_searcher.rb +1 -1
  25. data/app/views/layouts/quick_search/_google_analytics.html.erb +4 -9
  26. data/app/views/layouts/quick_search/_search_form.html.erb +2 -2
  27. data/app/views/layouts/quick_search/application.html.erb +4 -0
  28. data/app/views/quick_search/appstats/_date_range.html.erb +14 -13
  29. data/app/views/quick_search/appstats/_graph_best_bets.html.erb +6 -0
  30. data/app/views/quick_search/appstats/_graph_clicks_overview.html.erb +12 -0
  31. data/app/views/quick_search/appstats/_graph_general_statistics.html.erb +19 -0
  32. data/app/views/quick_search/appstats/_graph_sessions_details.html.erb +19 -0
  33. data/app/views/quick_search/appstats/_graph_sessions_overview.html.erb +9 -0
  34. data/app/views/quick_search/appstats/_graph_spelling_suggestions.html.erb +6 -0
  35. data/app/views/quick_search/appstats/_graph_top_searches.html.erb +6 -0
  36. data/app/views/quick_search/appstats/_menu.html.erb +16 -3
  37. data/app/views/quick_search/appstats/_sessions_details_device_filters.html.erb +9 -0
  38. data/app/views/quick_search/appstats/_sessions_details_location_filters.html.erb +9 -0
  39. data/app/views/quick_search/appstats/_sessions_overview_graph_filters.html.erb +9 -0
  40. data/app/views/quick_search/appstats/clicks_overview.html.erb +3 -14
  41. data/app/views/quick_search/appstats/index.html.erb +3 -23
  42. data/app/views/quick_search/appstats/sessions_details.html.erb +9 -0
  43. data/app/views/quick_search/appstats/sessions_overview.html.erb +9 -0
  44. data/app/views/quick_search/appstats/top_searches.html.erb +3 -32
  45. data/app/views/quick_search/appstats/top_spot.html.erb +7 -7
  46. data/app/views/quick_search/search/_module_with_paging.html.erb +1 -1
  47. data/app/views/quick_search/search/_result_title.html.erb +1 -1
  48. data/config/quicksearch_config.yml.example +3 -0
  49. data/config/routes.rb +31 -3
  50. data/db/migrate/20161201141003_create_sessions.rb +12 -0
  51. data/db/migrate/20161201142811_change_event_columns.rb +11 -0
  52. data/db/migrate/20161201143901_add_session_id_to_searches.rb +5 -0
  53. data/db/migrate/20161212192454_add_date_string_columns.rb +11 -0
  54. data/db/seeds.rb +83 -7
  55. data/lib/generators/quick_search/templates/quick_search_typeahead.js +253 -0
  56. data/lib/generators/quick_search/typeahead_generator.rb +162 -0
  57. data/lib/quick_search/engine.rb +1 -0
  58. data/lib/quick_search/version.rb +1 -1
  59. data/test/dummy/db/schema.rb +25 -6
  60. data/test/dummy/log/benchmark.log +121 -241
  61. data/test/dummy/log/development.log +236 -0
  62. data/test/dummy/tmp/cache/assets/sprockets/v3.0/0b/0bXp2nnjPLNuYQRAMo1RkhNr8RSC8vqZo_7soo0XZTE.cache +1 -0
  63. data/test/dummy/tmp/cache/assets/sprockets/v3.0/3p/3pAfj3gt9c9rLmOrDkMvW2r6Swm5YhuGQ1EN3va9LDE.cache +6 -0
  64. data/test/dummy/tmp/cache/assets/sprockets/v3.0/3r/3r7qg-LCIcD5882b6GKgRTZycpAlhoSNJccVH06yRCQ.cache +3 -0
  65. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{UA/UAOIipK_ejTAUFI6XxASoweyUiC3t64VfVzV-MfsFD8.cache → 4h/4hLiHzJdffGLMZCeIkbisOdFQSyqs54YkoZpBpGotVk.cache} +0 -0
  66. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{Hr/Hr-kCuPY72nIKAdnsDTz9JqXd_z1WL6FSGMAq4HppZg.cache → 5Y/5Yh8GnSzywqhuMWMloKnL9FnuIxa9rBw6hN2Ecv0urw.cache} +0 -0
  67. data/test/dummy/tmp/cache/assets/sprockets/v3.0/8h/8hHsBZ0Il3GVKNj0HgTh-s-XpPVkHrcSY0EJ4v548sk.cache +4 -0
  68. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{fI/fInu_Gja7ik39t207tqj4CtlL0akQRbjD2T7qEtd0zE.cache → 8n/8nKAboU_CeVIX30-Q9BtSV5TXdE07WU8hO0OQ0N3KOs.cache} +0 -0
  69. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{dz/dzCaa80YgyvJINTgsyrVhKv9UQ1OC82GfAsA-IP96iQ.cache → Af/Afug8duevWkZFPkOMnZ5z_IbRT6JMZo4jw1QFmLiXXA.cache} +0 -0
  70. data/test/dummy/tmp/cache/assets/sprockets/v3.0/C2/C24SLDoUbdiHRmPZx5lIZ3XPXmn9FDq7j7gZmbpDtcE.cache +1 -0
  71. data/test/dummy/tmp/cache/assets/sprockets/v3.0/CH/CHuDjj47dHGqRD3uN1JNwnlFyjjEKB09KR6WVsLj2Do.cache +3 -0
  72. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Ci/Cif3NOwhu5i6Qm3iRv2gNJLi_cu8yDVz0Nrq8TZu1tc.cache +3 -0
  73. data/test/dummy/tmp/cache/assets/sprockets/v3.0/FQ/FQWd8V-Pek1LBAfW9vzcG-FpWySsPRhMVzH9DlPxLWU.cache +1 -0
  74. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Fa/FaYEz8CfLTkxVZvnbgKGvJMhhBwNvEgsZgUwaHNRlaQ.cache +0 -0
  75. data/test/dummy/tmp/cache/assets/sprockets/v3.0/IA/IALQq6ICbs3VBSRsaUmGuN73Qv5wDfWTP86VmMh_sVs.cache +0 -0
  76. data/test/dummy/tmp/cache/assets/sprockets/v3.0/K1/K1jnTrW-_s64kqCA6fnoQ-ID-rDaGX34J-TNPyzDbLs.cache +1 -0
  77. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Mz/MzXE1Go_GKyu6AvJPhw394BtPM_jVTf0hMwobBi15tk.cache +1 -0
  78. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Os/OsIhoBDw5N2AWGQ-EMK79tgwPS1AC6Dy_gMSJdOv2Ss.cache +0 -0
  79. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{B5/B5ptGSZGmgHvoK2UDCAtpMhWxJEKxKFhyL9ACeBfzQk.cache → PC/PCth9nzoRrp5GI3Ut8FxlGpXjH63V7k2p4PRwfyu2sQ.cache} +0 -0
  80. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Pw/Pw3Jv2p7MsQz6pttCZig6VMVQ30sRyIDRS-2nM32yqY.cache +3 -0
  81. data/test/dummy/tmp/cache/assets/sprockets/v3.0/RI/RIOgtcuvLAJZl62RvAzqine3jCO3i2qyhswiW2G1bw8.cache +3 -0
  82. data/test/dummy/tmp/cache/assets/sprockets/v3.0/R_/R_6WhfFaPUAOAFfFd4bgUonI5aB4Fa6iOMY2J6G3U98.cache +4 -0
  83. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{pj/pjJDYrBipDo6Tr9_-W4UDzfZZ4UPTy9YpXrBQFRXnlo.cache → S3/S3gPdA-Zqya5r6wNhnlwqGV3B3-GDaA8i1fL_jo66AI.cache} +0 -0
  84. data/test/dummy/tmp/cache/assets/sprockets/v3.0/SS/SS3eYrFE6ghi5G_3POW760Y4BWo5OAaK5QjDrRGXOcs.cache +1 -0
  85. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{jj/jjdQCKZsewws2eRP7b2AVxRMvseOp8djjlDkB8UOtac.cache → Sq/SqjdxmYsdurpx9xk1ks32WEtf3D9_2BysE7RMxnOlmU.cache} +1 -1
  86. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{os/os0qWv3yDfep0e7vVSGTgfn_n3xc4C4QZgmMzLQn0Ks.cache → Ta/TaJnQ_l0sfSsDOyVoi-S-KZQ6vIwzBhmS6hmNA_n8ug.cache} +0 -0
  87. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{o6/o6ZQbq1aBFuVrE2xLbSbyZH2736sWM3Yj4ky9chT1bI.cache → X_/X_CxFXEdZo2C_in7FopAuAbplHn6W_V0SXyVQAMnTzI.cache} +0 -0
  88. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{sR/sRU-UT8qK_38kkiFDXKBPogO1oOZuSvJFH-pgqPL_Ms.cache → Yh/YhRWll6bzgYE6Dmc5fVKbHyv5om4y3-27UrWeUbQcGk.cache} +0 -0
  89. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{OD/ODHapE0li3WI1laVJoksoI5nmQd-8dDJXq_VxS4XefE.cache → _u/_uiA10I5HyJR94Tbc4a06CzdrQCAXX7Iz86WS5PKDQo.cache} +0 -0
  90. data/test/dummy/tmp/cache/assets/sprockets/v3.0/ai/ainCVF4dxXf85KoOLUSHvMMGg0qBNc7Vg-s-9v2ENJI.cache +4 -0
  91. data/test/dummy/tmp/cache/assets/sprockets/v3.0/cQ/cQqt0ri9GFvVnIs1pJUugIJs5tDuGQMMntW5CwpK6YY.cache +1 -0
  92. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{1n/1nStVaWIQEwAlksUpB1fInwq6v3TikPvuRaSwwCnCLQ.cache → e0/e0Y2Gz8ftaW2O0RjoN2iWguitI-JQZckScAZqlQYkng.cache} +0 -0
  93. data/test/dummy/tmp/cache/assets/sprockets/v3.0/iH/iHnlNNY3rfdbVqt8LZBfcDQYONR8dA8LmxA9fv5paro.cache +2 -0
  94. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{F0/F0oWBwLHpGFYNNtnGav-dJFH8rMDpNA5L8_aWm5Wq34.cache → ir/irxgIDPIY6Wz6fHWYp1dIrqAT1_8oa6-oiwqbFE_e-w.cache} +0 -0
  95. data/test/dummy/tmp/cache/assets/sprockets/v3.0/kn/knUtyVrg7nMIPldZa4MHhe7cC9eMchLZS4mc5HDWgDY.cache +1 -0
  96. data/test/dummy/tmp/cache/assets/sprockets/v3.0/ks/ks4DoIuUV6oUzoiXoPh7sW1k5ExNoUkR7ICjwuRnAT0.cache +1 -0
  97. data/test/dummy/tmp/cache/assets/sprockets/v3.0/m3/m375QQakjXd_2Trpvdot-i90nmeCCh0HWtzS66ZvxUU.cache +1 -0
  98. data/test/dummy/tmp/cache/assets/sprockets/v3.0/rQ/rQ_FgYkT2hdw9A6XmJKGgVb2dMtCrWWHwtS0X1_kdl0.cache +1 -0
  99. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{pw/pwiIhcWPwTuy5Miz5zqOQGYFx-K3TbaCVQ6ls7EBkk0.cache → rU/rUqFWwHqrT5jeIFCEezOPsMMZiAU7ri6RKBGS2ezAfg.cache} +0 -0
  100. data/test/dummy/tmp/cache/assets/sprockets/v3.0/{pO/pO8hHVwybv1ZJBdPa2YC6bjqmk020OYFl85TYxtmrWQ.cache → rq/rqf5-J6k_zLc9ZlUgMj28nviKtRsko3-iEp_m_uNeY0.cache} +0 -0
  101. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  102. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  103. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  104. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  105. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  106. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  107. metadata +143 -119
  108. data/app/assets/javascripts/quick_search/appstats.js +0 -8
  109. data/app/views/quick_search/appstats/_click_count_table.html.erb +0 -23
  110. data/app/views/quick_search/appstats/_top_spot_report_table.html.erb +0 -33
  111. data/app/views/quick_search/appstats/module_click_detail.html.erb +0 -32
  112. data/app/views/quick_search/appstats/top_spot_detail.html.erb +0 -35
  113. data/test/dummy/tmp/cache/assets/sprockets/v3.0/0_/0_w_ydOOEFQOoETxJa-IxKppQocaiDwM5V08djCPYso.cache +0 -3
  114. data/test/dummy/tmp/cache/assets/sprockets/v3.0/3m/3mWfBeEpm_bJvaPQZYvnnAVtE_2helOWh_Tv-cEflrk.cache +0 -5
  115. data/test/dummy/tmp/cache/assets/sprockets/v3.0/4s/4sg52OgD5l4qx4GnojCVxg2itqwqb-KRanEdmdMus3Y.cache +0 -0
  116. data/test/dummy/tmp/cache/assets/sprockets/v3.0/5U/5UPnsccuZFLk9OqbmFMvB55mbvGptApx9Z55HWjEN90.cache +0 -3
  117. data/test/dummy/tmp/cache/assets/sprockets/v3.0/9a/9acKNgyVD8_WK401tMy6Ce98g-LS4VpwFvGQdzCV9mI.cache +0 -0
  118. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Ba/BaymviI8cwAJQMOIzWCf6Lt-PM2sNFTvY9hQKsfDNNw.cache +0 -3
  119. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Dd/DdFHg3C_voetfvKGKX7t8PGzZlkO45-huHNtaou5rxg.cache +0 -1
  120. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Fp/FprCQeeZguht46WNSpA3Y3JHKehovFXrj63nDrvvwFE.cache +0 -1
  121. data/test/dummy/tmp/cache/assets/sprockets/v3.0/GJ/GJrfWiarIi0Dt6sdKVMfUF1cdZ6NbqZxXHhzNbFtpBw.cache +0 -1
  122. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Gp/GpoziOxxB-hisco0rl7Nnsnw0UWud7R9Yge2Q3o1eN4.cache +0 -2
  123. data/test/dummy/tmp/cache/assets/sprockets/v3.0/It/It5VkJv-53-LICLKZWWyp2WcbD86DGotAL0MY3hhfpg.cache +0 -6
  124. data/test/dummy/tmp/cache/assets/sprockets/v3.0/O6/O6dw148Xya7NYlsRFMEzJyvRcySM6iOL1jRUpdQo9B8.cache +0 -1
  125. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Oj/OjMkKTW7OkthjJAQHDID8GV30IZC9F0dJ1zGiFame9U.cache +0 -4
  126. data/test/dummy/tmp/cache/assets/sprockets/v3.0/SU/SUtZ_ltPqy9A9A-_zqNPhNPvjxCOnrdqXHSZv5S9mOk.cache +0 -1
  127. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Tv/TvZgmm69VEMHUJQIBoij61oXMkgzp6whVo7l8mJ9qpw.cache +0 -3
  128. data/test/dummy/tmp/cache/assets/sprockets/v3.0/YJ/YJZdK01T1KTS84Mb6p_2TasmJNZeGZc4FMtBPCKGr_M.cache +0 -3
  129. data/test/dummy/tmp/cache/assets/sprockets/v3.0/ah/ahhhd3ysbQmVDNzq2L_rb4t6rqqdnqeY6ujztBoZDA8.cache +0 -1
  130. data/test/dummy/tmp/cache/assets/sprockets/v3.0/gv/gvZKkbgiExbcQrT4eo0HB-j4JoDRAxdmDjKRxeEdK0Y.cache +0 -1
  131. data/test/dummy/tmp/cache/assets/sprockets/v3.0/hj/hj_r-O1QjHJIPcZVnlB_FGPdIRAi9NkxpT2oXDRIsGE.cache +0 -1
  132. data/test/dummy/tmp/cache/assets/sprockets/v3.0/o2/o2V1ahk8vFR6sXWNh5BB7w6MBj04zCyX-o1xFZONydE.cache +0 -1
  133. data/test/dummy/tmp/cache/assets/sprockets/v3.0/tE/tEO71oPPFXN_2c4p39nU0OiF9ARg13fjdQlnPqAx1KY.cache +0 -4
  134. data/test/dummy/tmp/cache/assets/sprockets/v3.0/tW/tWFRsAv4WWu2ch-x3AJZd3KOvBXG3MwH6Q6Xn-tFBtw.cache +0 -1
  135. data/test/dummy/tmp/cache/assets/sprockets/v3.0/x6/x6W754ylumshr3wBUHHmcgyWs8UR3Od7FaI6tqvjKmk.cache +0 -1
  136. data/test/dummy/tmp/cache/assets/sprockets/v3.0/yy/yyJ1P7RQciOiW8TgQiu1RMDdbWd4rqxd2BGU9HkY3L8.cache +0 -0
@@ -0,0 +1,165 @@
1
+ module QuickSearch
2
+
3
+ ##
4
+ # This class handles QuickSearch's internal logging of queries, events (clicks & serves), and sessions
5
+
6
+ class LoggingController < ApplicationController
7
+
8
+ include QuickSearch::OnCampus
9
+
10
+ before_action :handle_session
11
+ protect_from_forgery except: :log_event
12
+
13
+ ##
14
+ # Logs a search to the database
15
+ #
16
+ # This is an API endpoint for logging a search. It requires that at least a search query and a page are
17
+ # present in the query parameters. It returns a 200 OK HTTP status if the request was successful, or
18
+ # an 400 BAD REQUEST HTTP status if any parameters are missing.
19
+
20
+ def log_search
21
+ if params[:query].present? && params[:page].present?
22
+ @session.searches.create(query: params[:query], page: params[:page])
23
+ head :ok
24
+ else
25
+ head :bad_request
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Logs an event to the database. Typically, these can be clicks or serves.
31
+ #
32
+ # This is an API endpoint for logging an event. It requires that at least a TODO are
33
+ # present in the query parameters. It returns a 200 OK HTTP status if the request was successful, or
34
+ # an 400 BAD REQUEST HTTP status if any parameters are missing. This endpoint supports JSONP requests.
35
+
36
+ def log_event
37
+ if params[:category].present? && params[:event_action].present? && params[:label].present?
38
+ # if an action isn't passed in, assume that it is a click
39
+ action = params.fetch(:action_type, 'click')
40
+
41
+ # create a new event on the current session
42
+ @session.events.create(category: params[:category], item: params[:event_action], query: params[:label][0..250], action: action)
43
+
44
+ if params[:ga].present? and params[:ga]
45
+ send_event_to_ga(params[:category], params[:event_action], params[:label])
46
+ end
47
+
48
+ # check whether this is a jsonp request
49
+ if params[:callback].present?
50
+ render :json => { 'response': 'success' }, :callback => params[:callback]
51
+ else
52
+ render :json => { 'response': 'success' }
53
+ end
54
+ else
55
+ head :bad_request
56
+ end
57
+ end
58
+
59
+
60
+ private
61
+
62
+ ##
63
+ # Logs an event to Google Analytics using the Measurement Protocol API
64
+ # https://developers.google.com/analytics/devguides/collection/protocol/v1/
65
+
66
+ def send_event_to_ga(category, action, label)
67
+ # google_analytics_client_id is a UUID that identifies a particular client to the GA Measurement Protocol API
68
+ if QuickSearch::Engine::APP_CONFIG['google_analytics_tracking_id'].blank? or QuickSearch::Engine::APP_CONFIG['google_analytics_client_id'].blank?
69
+ return false
70
+ end
71
+
72
+ # Pass along the client user agent and IP address so it is associated
73
+ # with the event in Google Analytics
74
+ params = {
75
+ v: 1,
76
+ t: 'event',
77
+ tid: QuickSearch::Engine::APP_CONFIG['google_analytics_tracking_id'],
78
+ cid: QuickSearch::Engine::APP_CONFIG['google_analytics_client_id'],
79
+ ec: category,
80
+ ea: action,
81
+ el: label,
82
+ uip: request.remote_ip,
83
+ ua: request.user_agent,
84
+ }
85
+
86
+ client = HTTPClient.new
87
+ url = "https://www.google-analytics.com/collect?" + params.to_query
88
+
89
+ # The measurement protocol API does not validate responses.
90
+ # The best way to test this is testing the query string against:
91
+ # https://ga-dev-tools.appspot.com/hit-builder/
92
+
93
+ client.post(url)
94
+
95
+ end
96
+
97
+ ##
98
+ # Handles creating/updating a session on every request
99
+
100
+ def handle_session
101
+ if is_existing_session?
102
+ update_session
103
+ else
104
+ new_session
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Returns true if current request has an existing session, false otherwise
110
+
111
+ def is_existing_session?
112
+ cookies.has_key? :session_id and Session.find_by(session_uuid: cookies[:session_id])
113
+ end
114
+
115
+ ##
116
+ # Returns true if current request was from a mobile device
117
+ #
118
+ # Uses User-Agent from request to make the determination, which may not be all-encompassing
119
+ # but works for most modern devices/browsers (iOS, Android). Looks for the string "Mobi" within
120
+ # the user-agent, which normally contains either Mobi or Mobile if the request was from a mobile browser
121
+
122
+ def is_mobile?
123
+ # TODO: better test for mobile?
124
+ # Recommended here as simple test: https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
125
+ request.user_agent.include? "Mobi"
126
+ end
127
+
128
+ ##
129
+ # Creates a new session, and logs it in the database
130
+ #
131
+ # A session is tracked by a UUID that is stored in a cookie, and has a 5 minute expiry time.
132
+ # Sessions are stored in the database with the time they were initiated, their expiry time (or end time),
133
+ # whether the request originated from a campus IP address, and whether the request originated from a mobile device
134
+
135
+ def new_session
136
+ on_campus = on_campus?(request.remote_ip)
137
+ is_mobile = is_mobile?
138
+ session_expiry = 5.minutes.from_now
139
+ session_uuid = SecureRandom.uuid
140
+
141
+ # create session in db
142
+ @session = Session.create(session_uuid: session_uuid, expiry: session_expiry, on_campus: on_campus, is_mobile: is_mobile)
143
+ # set cookie
144
+ cookies[:session_id] = { :value => session_uuid, :expires => session_expiry }
145
+ end
146
+
147
+ ##
148
+ # Updates a session's expiration time on cookie and in database
149
+ #
150
+ # When a request is made with a non-expired session, the expiration time is updated to 5 minutes from the current time.
151
+ # This update is reflected in the cookie as well as in the database entry for the session.
152
+
153
+ def update_session
154
+ # update session expiry in the database
155
+ session_id = cookies[:session_id]
156
+ @session = Session.find_by session_uuid: session_id
157
+ @session.expiry = 5.minutes.from_now
158
+ @session.save
159
+
160
+ # update session expiry on cookie
161
+ cookies[:session_id] = { :value => session_id, :expires => @session.expiry }
162
+ end
163
+
164
+ end
165
+ end
@@ -12,7 +12,6 @@ module QuickSearch
12
12
 
13
13
  before_action :doi_trap, :log_query
14
14
  after_action :realtime_message, only: [:index]
15
- protect_from_forgery except: :log_event
16
15
 
17
16
  def index
18
17
  loaded_searches
@@ -114,28 +113,6 @@ module QuickSearch
114
113
  end
115
114
  end
116
115
 
117
- def log_search
118
- if params[:query].present? && params[:page].present?
119
- Search.create(query: params[:query], page: params[:page])
120
- head :ok
121
- else
122
- head :bad_request
123
- end
124
- end
125
-
126
- def log_event
127
- if params[:category].present? && params[:event_action].present? && params[:label].present?
128
- Event.create(category: params[:category], action: params[:event_action], label: params[:label][0..250])
129
- if params[:callback].present?
130
- head :ok, content_type: 'text/javascript'
131
- else
132
- head :ok
133
- end
134
- else
135
- head :bad_request
136
- end
137
- end
138
-
139
116
  private
140
117
 
141
118
  def http_search(endpoint = 'defaults', page_to_render = :index)
@@ -238,6 +215,7 @@ module QuickSearch
238
215
  result
239
216
  end
240
217
 
218
+ # TODO: move this --- is this necessary?
241
219
  def log_query
242
220
  if search_in_params?
243
221
  @log_query = filter_query(params_q_scrubbed)
@@ -0,0 +1,48 @@
1
+ module QuickSearch
2
+ class TypeaheadController < ApplicationController
3
+ protect_from_forgery except: :typeahead
4
+
5
+ # This method should return a list of search suggestions for a given searcher
6
+ # It should return errors if there is no param called 'searcher', if the searcher does not exist
7
+ # or if the searcher doesn't implement the 'typeahead' method
8
+ # Otherwise, it should return the result of calling the 'typeahead' method as JSON
9
+
10
+ def typeahead
11
+ # :searcher param expected to be name of a searcher with underscores, ie: ematrix_journal
12
+ searcher = params[:searcher]
13
+ query = params[:q] or ''
14
+ total = params[:total] or 3
15
+
16
+ if searcher.blank?
17
+ logger.error "Typeahead request: no searcher param provided"
18
+ head :bad_request
19
+ return nil
20
+ end
21
+
22
+ searcher_string = "QuickSearch::#{searcher.camelize}Searcher"
23
+
24
+ begin
25
+ klass = searcher_string.constantize
26
+ rescue NameError
27
+ logger.error "Typeahead request: searcher #{searcher} does not exist"
28
+ head :bad_request
29
+ return nil
30
+ end
31
+
32
+ if klass.method_defined? :typeahead
33
+ typeahead_result = klass.new(HTTPClient.new, query, total).typeahead
34
+
35
+ if params.has_key? 'callback'
36
+ render json: typeahead_result, callback: params['callback']
37
+ else
38
+ render json: typeahead_result
39
+ end
40
+ else
41
+ logger.error "Typeahead request: searcher #{searcher} has no typeahead method"
42
+ head :bad_request
43
+ return nil
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -1,2 +1,8 @@
1
1
  class Event < ApplicationRecord
2
+ belongs_to :session
3
+
4
+ before_create do
5
+ self.created_at_string = DateTime.now.strftime("%Y-%m-%d")
6
+ end
7
+
2
8
  end
@@ -1,4 +1,11 @@
1
1
  class Search < ApplicationRecord
2
+
3
+ belongs_to :session
4
+
5
+ before_create do
6
+ self.created_at_string = DateTime.now.strftime("%Y-%m-%d")
7
+ end
8
+
2
9
 
3
10
  def self.get_most_frequent_searches
4
11
  frequent_searches = []
@@ -0,0 +1,9 @@
1
+ class Session < ApplicationRecord
2
+ has_many :searches
3
+ has_many :events
4
+
5
+ before_create do
6
+ self.created_at_string = DateTime.now.strftime("%Y-%m-%d")
7
+ end
8
+
9
+ end
@@ -56,7 +56,7 @@ module QuickSearch
56
56
  result = OpenStruct.new
57
57
  result.title = title(best_bet)
58
58
  result.link = link(best_bet)
59
- result.id = ''
59
+ result.id = best_bet['id']
60
60
  result.description = description(best_bet)
61
61
  result.best_bets_type = 'best-bets-regular'
62
62
 
@@ -1,15 +1,10 @@
1
1
  <% if Rails.env == 'production' or Rails.env == 'staging' %>
2
2
  <script>
3
- // Classic Analytics
4
- var _gaq = _gaq || [];
5
- _gaq.push(['_setAccount', "<%= QuickSearch::Engine::APP_CONFIG['google_analytics_tracking_id'] %>"]);
6
- _gaq.push(['_trackPageview']);
3
+ // Universal analytics
4
+ (function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,"script","//www.google-analytics.com/analytics.js","ga");
7
5
 
8
- (function() {
9
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
10
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
11
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
12
- })();
6
+ ga("create", "<%= QuickSearch::Engine::APP_CONFIG['google_analytics_tracking_id'] %>", "auto");
7
+ ga("send", "pageview");
13
8
 
14
9
  </script>
15
10
  <% end %>
@@ -4,9 +4,9 @@
4
4
  <div class="row collapse">
5
5
  <div class="small-10 columns">
6
6
  <% if @query %>
7
- <input type="search" name="q" id="params-q" class="quicksearch-typeahead search-query" value="<%= @query %>" />
7
+ <input type="search" name="q" id="params-q" class="search-query" value="<%= @query %>" />
8
8
  <% else %>
9
- <input type="search" name="q" id="params-q" class="quicksearch-typeahead search-query" placeholder="<%= @search_form_placeholder %>" />
9
+ <input type="search" name="q" id="params-q" class="search-query" placeholder="<%= @search_form_placeholder %>" />
10
10
  <% end %>
11
11
  </div>
12
12
  <div class="small-2 columns">
@@ -12,6 +12,10 @@
12
12
  <%= javascript_include_tag 'application' %>
13
13
  <%= render '/layouts/quick_search/head' %>
14
14
  <%= render '/layouts/quick_search/google_analytics' %>
15
+
16
+ <!-- datepicker (temp) -->
17
+ <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
18
+ <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
15
19
  </head>
16
20
  <body class="<%= body_class %>" data-quicksearch-ga-query="<%= strip_tags(@log_query) %>">
17
21
  <div class="off-canvas-wrap">
@@ -1,15 +1,16 @@
1
1
  <% title('Foo') %>
2
- <div class="row">
3
- <div class="small-12 columns">
4
- <h1><%= @page_title %></h1>
5
- <div id="appstats-date-filter">
6
- <%= form_tag do %>
7
- <p>Start Date: <%= select_date @start_date, prefix: :start_date %></p>
8
- <p>End Date: <%= select_date @end_date, prefix: :end_date %></p>
9
- <p><%= submit_tag("Update") %></p>
10
- <% end %>
11
- </div>
12
- <p><%= "#{@start_date.to_formatted_s(:long) } to #{@end_date.to_formatted_s(:long)}" %> <a id="appstats-date-showhide">(Change start/end dates)</a></p>
13
- <p><%= "#{@days_in_sample} days in sample" %></p>
14
- </div>
2
+ <div class="medium-2 columns" style="position: fixed; right: 0;">
3
+ <div style="">
4
+ <label for="from" class="inputLabel"> From: </label>
5
+ <input id="from" type="text" name="from">
6
+ <label for="to" class="inputLabel"> To: </label>
7
+ <input id="to" type="text" name="to">
8
+ <p id="datePickerText"> Days in Sample: </p>
9
+ <p id="numDays"><%= "#{@days_in_sample}" %></p>
10
+ <% if @page_title=='spelling-suggestion'||@page_title=='best-bets-regular'||@page_title=='Top Searches' %>
11
+ <label for="num" class="inputLabel"> # Results: </label>
12
+ <input id="num" type="number" name="num" min="0" max="200">
13
+ <% end %>
14
+ <button id="dateButton" type="button"> Update </button>
15
+ <div>
15
16
  </div>
@@ -0,0 +1,6 @@
1
+ <h1><%= @page_title %></h1>
2
+ <h2>Most Frequent Best-Bets</h2>
3
+ <div class="graphGroup">
4
+ <div class="iconContainer" id="bbIconContainer"></div>
5
+ <svg id="graph_best_bets" width="840" height="240"></svg>
6
+ </div>
@@ -0,0 +1,12 @@
1
+ <h1><%= @page_title %></h1>
2
+ <h2>Frequency of Modules Clicked</h2>
3
+ <div class="graphGroup">
4
+ <div class="iconContainer" id="moduleIconContainer"></div>
5
+ <svg id="graph_module_clicks" width="840" height="240"></svg>
6
+ </div>
7
+
8
+ <h2>Frequency of Result-Type Guides Clicked</h2>
9
+ <div class="graphGroup">
10
+ <div class="iconContainer" id="resultIconContainer"></div>
11
+ <svg id="graph_result_clicks" width="840" height="240"></svg>
12
+ </div>
@@ -0,0 +1,19 @@
1
+ <h1><%= @page_title %></h1>
2
+ <div class="graphGroup">
3
+ <div class="iconContainer" id="tableIconContainer"></div>
4
+ <svg class="graph" id="graph_general_table" width="840" height="473"></svg>
5
+ </svg>
6
+ </div>
7
+
8
+ <div class="graphGroup">
9
+ <div class="iconContainer" id="graphIconContainer"></div>
10
+ <svg class="graph" id="graph_general_statistics" width="840" height="473">
11
+ </svg>
12
+ </div>
13
+
14
+ <div style="display: inline-block; width: 1000px;">
15
+ <svg class="graph_mini" id="mini0" width="280" height="158" onmouseover="lighten()" onmouseout="darken()"></svg>
16
+ <svg class="graph_mini" id="mini1" width="280" height="158" onmouseover="lighten()" onmouseout="darken()"></svg>
17
+ <svg class="graph_mini" id="mini2" width="280" height="158" onmouseover="lighten()" onmouseout="darken()"></svg>
18
+ <div class="clearfix"></div>
19
+ </div>
@@ -0,0 +1,19 @@
1
+ <h1><%= @page_title %></h1>
2
+ <h2> Sessions by Location </h2>
3
+ <div class="graphGroup">
4
+ <div class="iconContainer" id="locationIconContainer"></div>
5
+ <div style="float: left; display: inline-block;">
6
+ <svg id="graph_sessions_location" width="840" height="473"></svg>
7
+ </div>
8
+ <%= render partial: 'sessions_details_location_filters' %>
9
+ </div>
10
+
11
+ <div class="clearfix"></div>
12
+ <h2> Sessions by Access Device </h2>
13
+ <div class="graphGroup">
14
+ <div class="iconContainer" id="deviceIconContainer"></div>
15
+ <div style="float: left; display: inline-block;">
16
+ <svg id="graph_sessions_device" width="840" height="473"></svg>
17
+ </div>
18
+ <%= render partial: 'sessions_details_device_filters' %>
19
+ </div>
@@ -0,0 +1,9 @@
1
+ <h1><%= @page_title %></h1>
2
+ <h2>Sessions Overview</h2>
3
+ <div class="graphGroup">
4
+ <div class="iconContainer" id="sessIconContainer"></div>
5
+ <div style="float: left; display: inline-block;">
6
+ <svg id="graph_sessions_overview" width="840" height="473"></svg>
7
+ </div>
8
+ <%= render partial: 'sessions_overview_graph_filters' %>
9
+ </div>