quick_search-core 0.1.1 → 0.2.0

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.
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>