query_reviewer 0.1

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 (32) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +118 -0
  3. data/Rakefile +24 -0
  4. data/lib/query_reviewer/array_extensions.rb +29 -0
  5. data/lib/query_reviewer/controller_extensions.rb +65 -0
  6. data/lib/query_reviewer/mysql_adapter_extensions.rb +90 -0
  7. data/lib/query_reviewer/mysql_analyzer.rb +62 -0
  8. data/lib/query_reviewer/query_warning.rb +17 -0
  9. data/lib/query_reviewer/rails.rb +33 -0
  10. data/lib/query_reviewer/sql_query.rb +130 -0
  11. data/lib/query_reviewer/sql_query_collection.rb +103 -0
  12. data/lib/query_reviewer/sql_sub_query.rb +45 -0
  13. data/lib/query_reviewer/tasks.rb +8 -0
  14. data/lib/query_reviewer/views/_box.html.erb +11 -0
  15. data/lib/query_reviewer/views/_box_ajax.js +34 -0
  16. data/lib/query_reviewer/views/_box_body.html.erb +73 -0
  17. data/lib/query_reviewer/views/_box_disabled.html.erb +2 -0
  18. data/lib/query_reviewer/views/_box_header.html.erb +1 -0
  19. data/lib/query_reviewer/views/_box_includes.html.erb +234 -0
  20. data/lib/query_reviewer/views/_explain.html.erb +30 -0
  21. data/lib/query_reviewer/views/_js_includes.html.erb +68 -0
  22. data/lib/query_reviewer/views/_js_includes_new.html.erb +68 -0
  23. data/lib/query_reviewer/views/_profile.html.erb +26 -0
  24. data/lib/query_reviewer/views/_query_sql.html.erb +8 -0
  25. data/lib/query_reviewer/views/_query_trace.html.erb +31 -0
  26. data/lib/query_reviewer/views/_query_with_warning.html.erb +54 -0
  27. data/lib/query_reviewer/views/_spectrum.html.erb +10 -0
  28. data/lib/query_reviewer/views/_warning_no_query.html.erb +8 -0
  29. data/lib/query_reviewer/views/query_review_box_helper.rb +98 -0
  30. data/lib/query_reviewer.rb +54 -0
  31. data/query_reviewer_defaults.yml +39 -0
  32. metadata +96 -0
@@ -0,0 +1,103 @@
1
+ module QueryReviewer
2
+ # a collection of SQL SELECT queries
3
+ class SqlQueryCollection
4
+ COMMANDS = %w(SELECT DELETE INSERT UPDATE)
5
+
6
+ attr_reader :query_hash
7
+ attr_accessor :overhead_time
8
+ def initialize(query_hash = {})
9
+ @query_hash = query_hash
10
+ @overhead_time = 0.0
11
+ end
12
+
13
+ def queries
14
+ query_hash.values
15
+ end
16
+
17
+ def total_duration
18
+ self.queries.collect(&:durations).flatten.sum
19
+ end
20
+
21
+ def query_count
22
+ queries.collect(&:count).sum
23
+ end
24
+
25
+ def analyze!
26
+ self.queries.collect(&:analyze!)
27
+
28
+ @warnings = []
29
+
30
+ crit_severity = 9# ((QueryReviewer::CONFIGURATION["critical_severity"] + 10)/2).to_i
31
+ warn_severity = QueryReviewer::CONFIGURATION["critical_severity"] - 1 # ((QueryReviewer::CONFIGURATION["warn_severity"] + QueryReviewer::CONFIGURATION["critical_severity"])/2).to_i
32
+
33
+ COMMANDS.each do |command|
34
+ count = count_of_command(command)
35
+ if count > QueryReviewer::CONFIGURATION["critical_#{command.downcase}_count"]
36
+ warn(:severity => crit_severity, :problem => "#{count} #{command} queries on this page", :description => "Too many #{command} queries can severely slow down a page")
37
+ elsif count > QueryReviewer::CONFIGURATION["warn_#{command.downcase}_count"]
38
+ warn(:severity => warn_severity, :problem => "#{count} #{command} queries on this page", :description => "Too many #{command} queries can slow down a page")
39
+ end
40
+ end
41
+ end
42
+
43
+ def find_or_create_sql_query(sql, cols, run_time, profile, command, affected_rows)
44
+ sanitized_sql = SqlQuery.sanitize_strings_and_numbers_from_sql(sql)
45
+ trace = SqlQuery.generate_full_trace(Kernel.caller)
46
+ key = [sanitized_sql, trace]
47
+ if query_hash[key]
48
+ query_hash[key].add(sql, run_time, profile)
49
+ else
50
+ query_hash[key] = SqlQuery.new(sql, cols, trace, run_time, profile, command, affected_rows, sanitized_sql)
51
+ end
52
+ end
53
+
54
+ def warn(options)
55
+ @warnings << QueryWarning.new(options)
56
+ end
57
+
58
+ def warnings
59
+ self.queries.collect(&:warnings).flatten.sort{|a,b| b.severity <=> a.severity}
60
+ end
61
+
62
+ def without_warnings
63
+ self.queries.reject{|q| q.has_warnings?}.sort{|a,b| b.duration <=> a.duration}
64
+ end
65
+
66
+ def collection_warnings
67
+ @warnings
68
+ end
69
+
70
+ def max_severity
71
+ warnings.empty? && collection_warnings.empty? ? 0 : [warnings.empty? ? 0 : warnings.collect(&:severity).flatten.max, collection_warnings.empty? ? 0 : collection_warnings.collect(&:severity).flatten.max].max
72
+ end
73
+
74
+ def only_of_command(command, only_no_warnings = false)
75
+ qs = only_no_warnings ? self.without_warnings : self.queries
76
+ qs.select{|q| q.command == command}
77
+ end
78
+
79
+ def count_of_command(command, only_no_warnings = false)
80
+ only_of_command(command, only_no_warnings).collect(&:durations).collect(&:size).sum
81
+ end
82
+
83
+ def total_severity
84
+ warnings.collect(&:severity).sum
85
+ end
86
+
87
+ def total_with_warnings
88
+ queries.select(&:has_warnings?).length
89
+ end
90
+
91
+ def total_without_warnings
92
+ queries.length - total_with_warnings
93
+ end
94
+
95
+ def percent_with_warnings
96
+ queries.empty? ? 0 : (100.0 * total_with_warnings / queries.length).to_i
97
+ end
98
+
99
+ def percent_without_warnings
100
+ queries.empty? ? 0 : (100.0 * total_without_warnings / queries.length).to_i
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,45 @@
1
+ module QueryReviewer
2
+ # a single part of an SQL SELECT query
3
+ class SqlSubQuery < OpenStruct
4
+ include MysqlAnalyzer
5
+
6
+ delegate :sql, :to => :parent
7
+ attr_reader :cols, :warnings, :parent
8
+ def initialize(parent, cols)
9
+ @parent = parent
10
+ @warnings = []
11
+ @cols = cols.inject({}) {|memo, obj| memo[obj[0].to_s.downcase] = obj[1].to_s.downcase; memo }
12
+ @cols["query_type"] = @cols.delete("type")
13
+ super(@cols)
14
+ end
15
+
16
+ def analyze!
17
+ @warnings = []
18
+ adapter_name = ActiveRecord::Base.connection.instance_variable_get("@config")[:adapter]
19
+ adapter_name = 'mysql' if adapter_name == 'mysql2'
20
+ method_name = "do_#{adapter_name}_analysis!"
21
+ self.send(method_name.to_sym)
22
+ end
23
+
24
+ def table
25
+ @table[:table]
26
+ end
27
+
28
+ private
29
+
30
+ def warn(options)
31
+ if (options[:field])
32
+ field = options.delete(:field)
33
+ val = self.send(field)
34
+ options[:problem] = ("#{field.to_s.titleize}: #{val.blank? ? "(blank)" : val}")
35
+ end
36
+ options[:query] = self
37
+ options[:table] = self.table
38
+ @warnings << QueryWarning.new(options)
39
+ end
40
+
41
+ def praise(options)
42
+ # no credit, only pain
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ namespace :query_reviewer do
2
+ desc "Create a default config/query_reviewer.yml"
3
+ task :setup do
4
+ defaults_path = File.join(File.dirname(__FILE__), "../..", "query_reviewer_defaults.yml")
5
+ dest_path = File.join(Rails.root.to_s, "config", "query_reviewer.yml")
6
+ FileUtils.copy(defaults_path, dest_path)
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ <div id="query_review_parent" class="query_review_parent">
2
+ <div id="query_review_0" class="query_review_container">
3
+ <%= render :partial => "/box_includes"%>
4
+ <div class="query_review <%= parent_div_class %>" id = "query_review_header_0">
5
+ <%= render :partial => "/box_header" %>
6
+ </div>
7
+ <div class="query_review_details" id="query_review_details_0" style="display: none;">
8
+ <%= render :partial => enabled_by_cookie ? "/box_body" : "/box_disabled" %>
9
+ </div>
10
+ </div>
11
+ </div>
@@ -0,0 +1,34 @@
1
+ var i = 0;
2
+ var last = null;
3
+ for(i=0; i<10; i++) {
4
+ if(document.getElementById("query_review_"+i)) {
5
+ last = document.getElementById("query_review_"+i);
6
+ } else {
7
+ break;
8
+ }
9
+ }
10
+ if(i < 10) {
11
+ var container_div = document.createElement("div");
12
+ container_div.style.left = ""+(i * 30 + 1)+"px";
13
+ container_div.setAttribute("id", "query_review_"+i);
14
+ container_div.className = "query_review_container";
15
+ var ihtml = '<%= escape_javascript(render(:partial => "/box_includes"))%>';
16
+ ihtml += '<div class="query_review <%= parent_div_class %>" id = "query_review_header_'+i+'">';
17
+ ihtml += '<%= escape_javascript(render(:partial => "/box_header"))%>';
18
+ ihtml += '</div> ';
19
+ ihtml += '<div class="query_review_details" id="query_review_details_'+i+'" style="display: none;">';
20
+ ihtml += '<%= escape_javascript(render(:partial => enabled_by_cookie ? "/box_body" : "/box_disabled")) %>';
21
+ ihtml += '</div>';
22
+
23
+ container_div.innerHTML = ihtml;
24
+
25
+ var parent_div = document.getElementById("query_review_parent")
26
+ if(!parent_div) {
27
+ parent_div = document.createElement("div");
28
+ parent_div.setAttribute("id", "query_review_parent");
29
+ parent_div.className = "query_reivew_parent";
30
+ document.getElementById("body")[0].appendChild(parent_div);
31
+ }
32
+
33
+ parent_div.appendChild(container_div);
34
+ }
@@ -0,0 +1,73 @@
1
+ <p>Total queries: <span class="number"><%= @queries.query_count %></span>&nbsp;&nbsp;
2
+ <% if @total_time %>Total time: <span class="number" title="TOTAL TIME: <%= @total_time %>s QR_OVERHEAD: <%= @queries.overhead_time %>s">
3
+ <%= '%.3f' % (@total_time - @queries.overhead_time) %></span>s&nbsp;&nbsp;
4
+ <% end %>
5
+ MySQL Database Time: <span class="number"><%= '%.3f' % @queries.total_duration %></span>s</p>
6
+ <p class="indent">With warnings: <span class="number bad"><%= @queries.total_with_warnings %></span> (<%= @queries.percent_with_warnings %>%)</p>
7
+ <p class="indent">Without warnings: <span class="number good"><%= @queries.total_without_warnings %></span> (<%= @queries.percent_without_warnings %>%)</p>
8
+ <p>Type:
9
+ <% QueryReviewer::SqlQueryCollection::COMMANDS.each do |command| %>
10
+ <% next if @queries.count_of_command(command).zero? %>
11
+ <span class="number"><%= @queries.count_of_command(command) %></span> <%= command %>s&nbsp;&nbsp;
12
+ <% end %>
13
+ </p>
14
+ <% if warnings_no_query_sorted.length + queries_with_warnings_sorted.length > 0 %>
15
+ <div class="divider"></div>
16
+ <% if warnings_no_query_sorted_nonignored.length + queries_with_warnings_sorted_nonignored.length > 0 %>
17
+ <p class="title"><%= warnings_no_query_sorted_nonignored.length + queries_with_warnings_sorted_nonignored.length %> Errors:</p>
18
+ <ul>
19
+ <%= render :partial => "/warning_no_query", :collection => warnings_no_query_sorted_nonignored %>
20
+ <%= render :partial => "/query_with_warning", :collection => queries_with_warnings_sorted_nonignored %>
21
+ </ul>
22
+ <% end %>
23
+ <% if warnings_no_query_sorted_ignored.length + queries_with_warnings_sorted_ignored.length > 0 %>
24
+ <%= warnings_no_query_sorted_ignored.length + queries_with_warnings_sorted_ignored.length %> Warnings:
25
+ <ul id="query_review_ignored_warnings">
26
+ <%= render :partial => "/warning_no_query", :collection => warnings_no_query_sorted_ignored %>
27
+ <%= render :partial => "/query_with_warning", :collection => queries_with_warnings_sorted_ignored %>
28
+ </ul>
29
+ <% end %>
30
+ <% end %>
31
+ <div class="divider"></div>
32
+ <p class="title">Safe queries:</p>
33
+ <% if @queries.queries.empty? %>
34
+ No queries to display.
35
+ <% else %>
36
+ <% QueryReviewer::SqlQueryCollection::COMMANDS.reverse.each do |command| %>
37
+ <% next if @queries.count_of_command(command, true).zero? %>
38
+ <ul class="small">
39
+ <% @queries.only_of_command(command, true).each do |query| %>
40
+ <li>
41
+ <% if QueryReviewer::CONFIGURATION["production_data"] %>
42
+ <%= duration_with_color(query) %>s
43
+ <% end %>
44
+ <% if query.count > 1 %>
45
+ <b title="<%= query.count %> queries were executed with the same stack trace and similar SQL structure">
46
+ <%= query.count %> identical queries
47
+ </b>
48
+ <% end %>
49
+ <%= render :partial => "/query_sql", :locals=>{ :query_sql => query } %>
50
+ <% if query.select? %>
51
+ <a href="javascript: query_review_toggle('warning_<%= query.id %>_explain')" title="show/hide sql">EXPLN</a>
52
+ <% end %>
53
+ <% if QueryReviewer::CONFIGURATION["profiling"] && query.profile %>
54
+ <a href="javascript: query_review_toggle('warning_<%= query.id %>_profile')" title="show/hide profile">PROF</a>
55
+ <% end %>
56
+ <a href="javascript: query_review_toggle('warning_<%= query.id %>_trace')" title="show/hide stack trace">TRACE</a>
57
+ <div style="display: none" id="warning_<%= query.id %>_explain" class="indent small tbpadded">
58
+ <%= render :partial => "/explain", :locals => {:query => query} %>
59
+ </div>
60
+ <% if QueryReviewer::CONFIGURATION["profiling"] && query.profile %>
61
+ <div style="display: none" id="warning_<%= query.id %>_profile" class="indent small">
62
+ <%= render :partial => "/profile", :locals => {:query => query} %>
63
+ </div>
64
+ <% end %>
65
+ <div style="display: none" id="warning_<%= query.id %>_trace" class="indent small">
66
+ <%= render :partial => "/query_trace", :object => query.relevant_trace, :locals => {:query_id => query.id, :full_trace => query.full_trace} %>
67
+ </div>
68
+ </li>
69
+ <% end %>
70
+ </ul>
71
+ <% end %>
72
+ <% end %>
73
+ <p id="query_review_disable_link"><a href="javascript: eraseCookie('query_review_enabled'); query_review_hide('query_review_disable_link'); alert('Cookie successfully set.');">Disable analysis report</a> on next page load and from now on.</p>
@@ -0,0 +1,2 @@
1
+ <p>SQL analysis has been disabled for you. A cookie must be set to enable analysis. This generally slows down your browser, so it's only recommended for users analyzing SQL queries.</p>
2
+ <p id="query_review_enable_link"><b><a href="javascript: createCookie('query_review_enabled', '1'); query_review_hide('query_review_enable_link'); alert('Cookie successfully set.');">Enabled it</a> on next page load and from now on.</b></p>
@@ -0,0 +1 @@
1
+ <a href="#" onclick="query_review_toggle(this.parentNode.nextSibling.nextSibling.id); return false;">SQL <%= parent_div_status %></a>
@@ -0,0 +1,234 @@
1
+ <style>
2
+ div#query_review_parent {
3
+ position: absolute;
4
+ top: 1px;
5
+ left: 1px;
6
+ z-index: 100000;
7
+ }
8
+
9
+ div.query_review_container {
10
+ float: left;
11
+ }
12
+
13
+ div.query_review {
14
+ height: 18px;
15
+ margin: 1px;
16
+ opacity: 0.75;
17
+ padding-left: 3px;
18
+ padding-right: 3px;
19
+ border: 1px solid black;
20
+ font-size: 12px;
21
+ font-weight: bold;
22
+ text-align: left;
23
+ }
24
+
25
+ div.query_review.sql_ok {
26
+ background-color: #090;
27
+ color: #020;
28
+ }
29
+
30
+ div.query_review.sql_warning {
31
+ background-color: #FAE100;
32
+ color: #F05000;
33
+ }
34
+
35
+ div.query_review.sql_critical {
36
+ background-color: #F99;
37
+ color: #A00;
38
+ }
39
+
40
+ div.query_review.sql_disabled {
41
+ background-color: #BBB;
42
+ color: #555;
43
+ }
44
+
45
+ div.query_review a {
46
+ color: inherit;
47
+ text-decoration: none;
48
+ }
49
+
50
+ div.query_review_details {
51
+ width: 900px;
52
+ height: 600px;
53
+ opacity: 0.9;
54
+ margin: 1px;
55
+ padding: 2px;
56
+ border: 1px solid black;
57
+ font-size: 12px;
58
+ overflow: auto;
59
+ background-color: #DDD;
60
+ text-align: left;
61
+ text-indent: 0px;
62
+ color: black;
63
+ }
64
+
65
+ div.query_review_details p {
66
+ margin: 2px 0 2px 0;
67
+ padding: 0px;
68
+ }
69
+
70
+ div.query_review_details a {
71
+ color: #29ABE2;
72
+ }
73
+
74
+ div.query_review_details ul {
75
+ list-style-type: circle;
76
+ padding-left: 15px;
77
+ }
78
+
79
+ div.query_review_details li {
80
+ clear: both;
81
+ }
82
+
83
+ div.query_review_details code {
84
+ white-space: normal;
85
+ line-height: 120%;
86
+ }
87
+
88
+ div.query_review_details table.explain th {
89
+ cell-padding: 5px;
90
+ }
91
+
92
+ div.query_review_details .title {
93
+ font-weight: bold;
94
+ }
95
+
96
+ div.query_review_details .indent {
97
+ padding-left: 10px;
98
+ }
99
+
100
+ div.query_review_details .number {
101
+ font-weight: bold;
102
+ }
103
+
104
+ div.query_review_details .bad {
105
+ color: #900;
106
+ }
107
+
108
+ div.query_review_details .good {
109
+ color: #090;
110
+ }
111
+
112
+ div.query_review_details div.divider {
113
+ width: 504px;
114
+ position: relative;
115
+ left: -2px;
116
+ height: 1px;
117
+ border-top: 1px dashed black;
118
+ margin: 2px 0 2px 0;
119
+ }
120
+
121
+ div.query_review_details .small {
122
+ font-size: 10px;
123
+ }
124
+
125
+ div.query_review_details .tbpadded {
126
+ padding-top: 3px;
127
+ padding-bottom: 3px;
128
+ }
129
+
130
+ div.query_review_details .trace {
131
+ background-color: #0C1021;
132
+ color: #7F908A;
133
+ padding: 5px;
134
+ margin-right: 30px;
135
+ }
136
+
137
+ div.query_review_details .trace .bold {
138
+ color: white;
139
+ }
140
+
141
+ div.query_review_details div.spectrum_container {
142
+ margin-top: 4px;
143
+ width: 35px;
144
+ display: block;
145
+ float: left;
146
+ position: relative;
147
+ height: 14px;
148
+ margin-right: 5px;
149
+ }
150
+
151
+ div.query_review_details div.spectrum_elem {
152
+ width: 1px;
153
+ display: block;
154
+ float: left;
155
+ height: 10px;
156
+ }
157
+ div.query_review_details div.spectrum_pointer {
158
+ width: 2px;
159
+ display: block;
160
+ float: left;
161
+ position: absolute;
162
+ height: 14px;
163
+ background-color: black;
164
+ top: -2px;
165
+ }
166
+
167
+ div.query_review_details table {
168
+ background-color: white;
169
+ border: 1px solid gray;
170
+ padding: 0px;
171
+ margin: 0px;
172
+ }
173
+
174
+ div.query_review_details tbody {
175
+ border: 0px;
176
+ padding: 0px;
177
+ margin: 0px;
178
+ }
179
+
180
+ div.query_review_details thead {
181
+ border-bottom: 1px solid gray;
182
+ padding: 0px;
183
+ margin: 0px;
184
+ }
185
+
186
+
187
+ div.query_review_details tr {
188
+ border: 0px;
189
+ padding: 0px;
190
+ margin: 0px;
191
+ }
192
+
193
+ div.query_review_details td {
194
+ border: 1px solid gray;
195
+ padding: 3px;
196
+ margin: 0px;
197
+ overflow: hidden;
198
+ }
199
+
200
+ div.query_review_details th {
201
+ border: 1px solid gray;
202
+ padding: 3px;
203
+ margin: 0px;
204
+ }
205
+
206
+ </style>
207
+
208
+ <style>
209
+ .sql {
210
+ color: black;
211
+ }
212
+ .sql .String {
213
+ color: #009933;
214
+ }
215
+ .sql .Keyword {
216
+ color: #0000FF;
217
+ }
218
+ .sql .Constant {
219
+ color: #6782D3;
220
+ }
221
+ .sql .Number {
222
+ color: #0066FF;
223
+ }
224
+ .sql .Comment {
225
+ color: #0066FF;
226
+ font-style: italic;
227
+ }
228
+ </style>
229
+
230
+ <% if defined?(Rails::Railtie) %>
231
+ <%= render :partial => "/js_includes_new" %>
232
+ <% else %>
233
+ <%= render :partial => "/js_includes" %>
234
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <table class="explain">
2
+ <thead>
3
+ <tr>
4
+ <th>table</th>
5
+ <th>select_type</th>
6
+ <th>type</th>
7
+ <th>extra</th>
8
+ <th>possible_keys</th>
9
+ <th>key</th>
10
+ <th>key length</th>
11
+ <th>ref</th>
12
+ <th>rows</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% query.subqueries.each do |subquery| %>
17
+ <tr>
18
+ <td title="<%= h subquery.table %>"><%= h subquery.table %></td>
19
+ <td title="<%= h subquery.select_type %>"><%= h subquery.select_type %></td>
20
+ <td title="<%= h subquery.query_type %>"><%= h subquery.query_type %></td>
21
+ <td title="<%= h subquery.extra %>"><%= h subquery.extra %></td>
22
+ <td title="<%= h subquery.possible_keys %>"><%= h subquery.possible_keys %></td>
23
+ <td title="<%= h subquery.key %>"><%= h subquery.key %></td>
24
+ <td title="<%= h subquery.key_len %>"><%= h subquery.key_len %></td>
25
+ <td title="<%= h subquery.ref %>"><%= h subquery.ref %></td>
26
+ <td title="<%= h subquery.rows %>"><%= h subquery.rows %></td>
27
+ </tr>
28
+ <% end %>
29
+ </tbody>
30
+ </table>
@@ -0,0 +1,68 @@
1
+ <% javascript_tag do %>
2
+ //Super lame show/hide functions so we don't need any 3rd party JS libs
3
+ function query_review_show(id) {
4
+ document.getElementById(id).style.display = "block";
5
+ }
6
+
7
+ function query_review_hide(id) {
8
+ document.getElementById(id).style.display = "none";
9
+ }
10
+ function query_review_toggle(id) {
11
+ if(document.getElementById(id).style.display == "none") {
12
+ document.getElementById(id).style.display = "block";
13
+ } else {
14
+ document.getElementById(id).style.display = "none";
15
+ }
16
+ }
17
+ function createCookie(name,value,days) {
18
+ if (days) {
19
+ var date = new Date();
20
+ date.setTime(date.getTime()+(days*24*60*60*1000));
21
+ var expires = "; expires="+date.toGMTString();
22
+ }
23
+ else var expires = "";
24
+ document.cookie = name+"="+value+expires+"; path=/";
25
+ }
26
+
27
+ function readCookie(name) {
28
+ var nameEQ = name + "=";
29
+ var ca = document.cookie.split(';');
30
+ for(var i=0;i < ca.length;i++) {
31
+ var c = ca[i];
32
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
33
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
34
+ }
35
+ return null;
36
+ }
37
+
38
+ function eraseCookie(name) {
39
+ createCookie(name,"",-1);
40
+ }
41
+
42
+ function ignore_list() {
43
+ var ignore_list = readCookie("query_review_ignore_list")
44
+ if (!ignore_list)
45
+ ignore_list = []
46
+ else
47
+ ignore_list = ignore_list.split(",")
48
+ return ignore_list
49
+ }
50
+
51
+ function add_ignore_hash(h){
52
+ var list = ignore_list();
53
+ list[list.length] = h
54
+ createCookie("query_review_ignore_list", list.join(","))
55
+ }
56
+
57
+ function remove_ignore_hash(h) {
58
+ var list = ignore_list();
59
+ var new_list = []
60
+ for(var i=0; i<list.length; i++)
61
+ {
62
+ if(list[i].toString() != h.toString()) {
63
+ new_list[new_list.length] = list[i]
64
+ }
65
+ }
66
+ createCookie("query_review_ignore_list", new_list.join(","))
67
+ }
68
+ <% end %>
@@ -0,0 +1,68 @@
1
+ <%= javascript_tag do %>
2
+ //Super lame show/hide functions so we don't need any 3rd party JS libs
3
+ function query_review_show(id) {
4
+ document.getElementById(id).style.display = "block";
5
+ }
6
+
7
+ function query_review_hide(id) {
8
+ document.getElementById(id).style.display = "none";
9
+ }
10
+ function query_review_toggle(id) {
11
+ if(document.getElementById(id).style.display == "none") {
12
+ document.getElementById(id).style.display = "block";
13
+ } else {
14
+ document.getElementById(id).style.display = "none";
15
+ }
16
+ }
17
+ function createCookie(name,value,days) {
18
+ if (days) {
19
+ var date = new Date();
20
+ date.setTime(date.getTime()+(days*24*60*60*1000));
21
+ var expires = "; expires="+date.toGMTString();
22
+ }
23
+ else var expires = "";
24
+ document.cookie = name+"="+value+expires+"; path=/";
25
+ }
26
+
27
+ function readCookie(name) {
28
+ var nameEQ = name + "=";
29
+ var ca = document.cookie.split(';');
30
+ for(var i=0;i < ca.length;i++) {
31
+ var c = ca[i];
32
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
33
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
34
+ }
35
+ return null;
36
+ }
37
+
38
+ function eraseCookie(name) {
39
+ createCookie(name,"",-1);
40
+ }
41
+
42
+ function ignore_list() {
43
+ var ignore_list = readCookie("query_review_ignore_list")
44
+ if (!ignore_list)
45
+ ignore_list = []
46
+ else
47
+ ignore_list = ignore_list.split(",")
48
+ return ignore_list
49
+ }
50
+
51
+ function add_ignore_hash(h){
52
+ var list = ignore_list();
53
+ list[list.length] = h
54
+ createCookie("query_review_ignore_list", list.join(","))
55
+ }
56
+
57
+ function remove_ignore_hash(h) {
58
+ var list = ignore_list();
59
+ var new_list = []
60
+ for(var i=0; i<list.length; i++)
61
+ {
62
+ if(list[i].toString() != h.toString()) {
63
+ new_list[new_list.length] = list[i]
64
+ }
65
+ }
66
+ createCookie("query_review_ignore_list", new_list.join(","))
67
+ }
68
+ <% end %>