blazer 1.7.7 → 2.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +242 -33
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +621 -211
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +0 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Chart.js +15658 -10011
  13. data/app/assets/javascripts/blazer/Sortable.js +3413 -848
  14. data/app/assets/javascripts/blazer/ace/ace.js +21294 -4
  15. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1991 -3
  16. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -1
  17. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -1
  18. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -1
  19. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -1
  20. data/app/assets/javascripts/blazer/application.js +5 -3
  21. data/app/assets/javascripts/blazer/bootstrap.js +842 -628
  22. data/app/assets/javascripts/blazer/chartkick.js +2015 -1244
  23. data/app/assets/javascripts/blazer/daterangepicker.js +372 -299
  24. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  25. data/app/assets/javascripts/blazer/{jquery_ujs.js → jquery-ujs.js} +161 -75
  26. data/app/assets/javascripts/blazer/jquery.js +10126 -9562
  27. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +321 -259
  28. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1546 -0
  29. data/app/assets/javascripts/blazer/moment.js +5085 -2460
  30. data/app/assets/javascripts/blazer/queries.js +18 -4
  31. data/app/assets/javascripts/blazer/routes.js +3 -0
  32. data/app/assets/javascripts/blazer/selectize.js +3828 -3604
  33. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  34. data/app/assets/javascripts/blazer/stupidtable.js +254 -87
  35. data/app/assets/javascripts/blazer/vue.js +11175 -6676
  36. data/app/assets/stylesheets/blazer/application.css +51 -6
  37. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  38. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  39. data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +1337 -711
  40. data/app/assets/stylesheets/blazer/{daterangepicker-bs3.css → daterangepicker.css} +207 -172
  41. data/app/assets/stylesheets/blazer/{selectize.default.css → selectize.css} +26 -10
  42. data/app/controllers/blazer/base_controller.rb +73 -46
  43. data/app/controllers/blazer/checks_controller.rb +1 -1
  44. data/app/controllers/blazer/dashboards_controller.rb +7 -13
  45. data/app/controllers/blazer/queries_controller.rb +171 -51
  46. data/app/controllers/blazer/uploads_controller.rb +147 -0
  47. data/app/helpers/blazer/base_helper.rb +6 -16
  48. data/app/models/blazer/audit.rb +3 -3
  49. data/app/models/blazer/check.rb +31 -5
  50. data/app/models/blazer/dashboard.rb +6 -2
  51. data/app/models/blazer/dashboard_query.rb +1 -1
  52. data/app/models/blazer/query.rb +30 -4
  53. data/app/models/blazer/record.rb +5 -0
  54. data/app/models/blazer/upload.rb +11 -0
  55. data/app/models/blazer/uploads_connection.rb +7 -0
  56. data/app/views/blazer/_nav.html.erb +3 -1
  57. data/app/views/blazer/_variables.html.erb +48 -23
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +1 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +1 -0
  60. data/app/views/blazer/checks/_form.html.erb +17 -9
  61. data/app/views/blazer/checks/edit.html.erb +2 -0
  62. data/app/views/blazer/checks/index.html.erb +37 -5
  63. data/app/views/blazer/checks/new.html.erb +2 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +5 -5
  65. data/app/views/blazer/dashboards/edit.html.erb +2 -0
  66. data/app/views/blazer/dashboards/new.html.erb +2 -0
  67. data/app/views/blazer/dashboards/show.html.erb +13 -7
  68. data/app/views/blazer/queries/_caching.html.erb +16 -0
  69. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  70. data/app/views/blazer/queries/_form.html.erb +23 -13
  71. data/app/views/blazer/queries/docs.html.erb +137 -0
  72. data/app/views/blazer/queries/home.html.erb +21 -7
  73. data/app/views/blazer/queries/run.html.erb +64 -29
  74. data/app/views/blazer/queries/schema.html.erb +44 -7
  75. data/app/views/blazer/queries/show.html.erb +15 -8
  76. data/app/views/blazer/uploads/_form.html.erb +27 -0
  77. data/app/views/blazer/uploads/edit.html.erb +3 -0
  78. data/app/views/blazer/uploads/index.html.erb +55 -0
  79. data/app/views/blazer/uploads/new.html.erb +3 -0
  80. data/app/views/layouts/blazer/application.html.erb +10 -5
  81. data/config/routes.rb +10 -1
  82. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  83. data/lib/blazer/adapters/base_adapter.rb +24 -1
  84. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  85. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  86. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  87. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  88. data/lib/blazer/adapters/elasticsearch_adapter.rb +30 -18
  89. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  90. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  91. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  92. data/lib/blazer/adapters/mongodb_adapter.rb +5 -1
  93. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  94. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  95. data/lib/blazer/adapters/presto_adapter.rb +9 -0
  96. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  97. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  98. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  99. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  100. data/lib/blazer/adapters/sql_adapter.rb +187 -20
  101. data/{app/mailers → lib}/blazer/check_mailer.rb +0 -0
  102. data/lib/blazer/data_source.rb +107 -30
  103. data/lib/blazer/engine.rb +21 -23
  104. data/lib/blazer/result.rb +95 -29
  105. data/lib/blazer/run_statement.rb +8 -4
  106. data/lib/blazer/run_statement_job.rb +8 -9
  107. data/lib/blazer/slack_notifier.rb +94 -0
  108. data/lib/blazer/statement.rb +75 -0
  109. data/lib/blazer/version.rb +1 -1
  110. data/lib/blazer.rb +154 -26
  111. data/lib/generators/blazer/install_generator.rb +7 -18
  112. data/lib/generators/blazer/templates/{config.yml → config.yml.tt} +26 -3
  113. data/lib/generators/blazer/templates/{install.rb → install.rb.tt} +6 -4
  114. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  115. data/lib/generators/blazer/uploads_generator.rb +18 -0
  116. data/lib/tasks/blazer.rake +11 -1
  117. data/licenses/LICENSE-ace.txt +24 -0
  118. data/licenses/LICENSE-bootstrap.txt +21 -0
  119. data/licenses/LICENSE-chart.js.txt +9 -0
  120. data/licenses/LICENSE-chartkick.js.txt +22 -0
  121. data/licenses/LICENSE-daterangepicker.txt +21 -0
  122. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  123. data/licenses/LICENSE-highlight.js.txt +29 -0
  124. data/licenses/LICENSE-jquery-ujs.txt +20 -0
  125. data/licenses/LICENSE-jquery.txt +20 -0
  126. data/licenses/LICENSE-moment-timezone.txt +20 -0
  127. data/licenses/LICENSE-moment.txt +22 -0
  128. data/licenses/LICENSE-selectize.txt +202 -0
  129. data/licenses/LICENSE-sortable.txt +21 -0
  130. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  131. data/licenses/LICENSE-stupidtable.txt +19 -0
  132. data/licenses/LICENSE-vue.txt +21 -0
  133. metadata +83 -53
  134. data/.gitignore +0 -14
  135. data/Gemfile +0 -4
  136. data/Rakefile +0 -1
  137. data/app/assets/javascripts/blazer/highlight.pack.js +0 -1
  138. data/app/assets/javascripts/blazer/moment-timezone.js +0 -1007
  139. data/blazer.gemspec +0 -26
@@ -0,0 +1,13 @@
1
+ function removeCommas(string) {
2
+ return string.replace(/,/g, "")
3
+ }
4
+
5
+ // Remove commas for integers and floats to fix issues with sorting in the stupidtable plugin
6
+ var stupidtableCustomSettings = {
7
+ "int": function(a, b) {
8
+ return parseInt(removeCommas(a), 10) - parseInt(removeCommas(b), 10);
9
+ },
10
+ "float": function(a, b) {
11
+ return parseFloat(removeCommas(a)) - parseFloat(removeCommas(b));
12
+ }
13
+ }
@@ -1,114 +1,281 @@
1
1
  // Stupid jQuery table plugin.
2
2
 
3
- // Call on a table
4
- // sortFns: Sort functions for your datatypes.
5
3
  (function($) {
6
-
7
4
  $.fn.stupidtable = function(sortFns) {
8
5
  return this.each(function() {
9
6
  var $table = $(this);
10
7
  sortFns = sortFns || {};
11
-
12
- // Merge sort functions with some default sort functions.
13
8
  sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
9
+ $table.data('sortFns', sortFns);
10
+ $table.stupidtable_build();
14
11
 
15
-
16
- // ==================================================== //
17
- // Begin execution! //
18
- // ==================================================== //
19
-
20
- // Do sorting when THs are clicked
21
- $table.on("click.stupidtable", "th", function() {
22
- var $this = $(this);
23
- var th_index = 0;
24
- var dir = $.fn.stupidtable.dir;
25
-
26
- $table.find("th").slice(0, $this.index()).each(function() {
27
- var cols = $(this).attr("colspan") || 1;
28
- th_index += parseInt(cols,10);
29
- });
30
-
31
- // Determine (and/or reverse) sorting direction, default `asc`
32
- var sort_dir = $this.data("sort-default") || dir.ASC;
33
- if ($this.data("sort-dir"))
34
- sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
35
-
36
- // Choose appropriate sorting function.
37
- var type = $this.data("sort") || null;
38
-
39
- // Prevent sorting if no type defined
40
- if (type === null) {
41
- return;
42
- }
43
-
44
- // Trigger `beforetablesort` event that calling scripts can hook into;
45
- // pass parameters for sorted column index and sorting direction
46
- $table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
47
- // More reliable method of forcing a redraw
48
- $table.css("display");
49
-
50
- // Run sorting asynchronously on a timout to force browser redraw after
51
- // `beforetablesort` callback. Also avoids locking up the browser too much.
52
- setTimeout(function() {
53
- // Gather the elements for this column
54
- var column = [];
55
- var sortMethod = sortFns[type];
56
- var trs = $table.children("tbody").children("tr");
57
-
58
- // Extract the data for the column that needs to be sorted and pair it up
59
- // with the TR itself into a tuple
60
- trs.each(function(index,tr) {
61
- var $e = $(tr).children().eq(th_index);
62
- var sort_val = $e.data("sort-value");
63
- var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
64
- column.push([order_by, tr]);
65
- });
66
-
67
- // Sort by the data-order-by value
68
- column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
69
- if (sort_dir != dir.ASC)
70
- column.reverse();
71
-
72
- // Replace the content of tbody with the sorted rows. Strangely (and
73
- // conveniently!) enough, .append accomplishes this for us.
74
- trs = $.map(column, function(kv) { return kv[1]; });
75
- $table.children("tbody").append(trs);
76
-
77
- // Reset siblings
78
- $table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
79
- $this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
80
-
81
- // Trigger `aftertablesort` event. Similar to `beforetablesort`
82
- $table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
83
- // More reliable method of forcing a redraw
84
- $table.css("display");
85
- }, 10);
12
+ $table.on("click.stupidtable", "thead th", function() {
13
+ $(this).stupidsort();
86
14
  });
15
+
16
+ // Sort th immediately if data-sort-onload="yes" is specified. Limit to
17
+ // the first one found - only one default sort column makes sense anyway.
18
+ var $th_onload_sort = $table.find("th[data-sort-onload=yes]").eq(0);
19
+ $th_onload_sort.stupidsort();
87
20
  });
88
21
  };
89
22
 
90
- // Enum containing sorting directions
23
+ // ------------------------------------------------------------------
24
+ // Default settings
25
+ // ------------------------------------------------------------------
26
+ $.fn.stupidtable.default_settings = {
27
+ should_redraw: function(sort_info){
28
+ return true;
29
+ },
30
+ will_manually_build_table: false
31
+ };
91
32
  $.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
92
-
93
33
  $.fn.stupidtable.default_sort_fns = {
94
34
  "int": function(a, b) {
95
- return parseInt(a.replace(/,/g, ''), 10) - parseInt(b.replace(/,/g, ''), 10);
35
+ return parseInt(a, 10) - parseInt(b, 10);
96
36
  },
97
37
  "float": function(a, b) {
98
38
  return parseFloat(a) - parseFloat(b);
99
39
  },
100
40
  "string": function(a, b) {
101
- if (a < b) return -1;
102
- if (a > b) return +1;
103
- return 0;
41
+ return a.toString().localeCompare(b.toString());
104
42
  },
105
43
  "string-ins": function(a, b) {
106
- a = a.toLowerCase();
107
- b = b.toLowerCase();
108
- if (a < b) return -1;
109
- if (a > b) return +1;
110
- return 0;
44
+ a = a.toString().toLocaleLowerCase();
45
+ b = b.toString().toLocaleLowerCase();
46
+ return a.localeCompare(b);
47
+ }
48
+ };
49
+
50
+ // Allow specification of settings on a per-table basis. Call on a table
51
+ // jquery object. Call *before* calling .stuidtable();
52
+ $.fn.stupidtable_settings = function(settings) {
53
+ return this.each(function() {
54
+ var $table = $(this);
55
+ var final_settings = $.extend({}, $.fn.stupidtable.default_settings, settings);
56
+ $table.stupidtable.settings = final_settings;
57
+ });
58
+ };
59
+
60
+
61
+ // Expects $("#mytable").stupidtable() to have already been called.
62
+ // Call on a table header.
63
+ $.fn.stupidsort = function(force_direction){
64
+ var $this_th = $(this);
65
+ var datatype = $this_th.data("sort") || null;
66
+
67
+ // No datatype? Nothing to do.
68
+ if (datatype === null) {
69
+ return;
70
+ }
71
+
72
+ var dir = $.fn.stupidtable.dir;
73
+ var $table = $this_th.closest("table");
74
+
75
+ var sort_info = {
76
+ $th: $this_th,
77
+ $table: $table,
78
+ datatype: datatype
79
+ };
80
+
81
+
82
+ // Bring in default settings if none provided
83
+ if(!$table.stupidtable.settings){
84
+ $table.stupidtable.settings = $.extend({}, $.fn.stupidtable.default_settings);
85
+ }
86
+
87
+ sort_info.compare_fn = $table.data('sortFns')[datatype];
88
+ sort_info.th_index = calculateTHIndex(sort_info);
89
+ sort_info.sort_dir = calculateSortDir(force_direction, sort_info);
90
+
91
+ $this_th.data("sort-dir", sort_info.sort_dir);
92
+ $table.trigger("beforetablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
93
+
94
+ // More reliable method of forcing a redraw
95
+ $table.css("display");
96
+
97
+ // Run sorting asynchronously on a timout to force browser redraw after
98
+ // `beforetablesort` callback. Also avoids locking up the browser too much.
99
+ setTimeout(function() {
100
+ if(!$table.stupidtable.settings.will_manually_build_table){
101
+ $table.stupidtable_build();
102
+ }
103
+ var table_structure = sortTable(sort_info);
104
+ var trs = getTableRowsFromTableStructure(table_structure, sort_info);
105
+
106
+ if(!$table.stupidtable.settings.should_redraw(sort_info)){
107
+ return;
108
+ }
109
+ $table.children("tbody").append(trs);
110
+
111
+ updateElementData(sort_info);
112
+ $table.trigger("aftertablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
113
+ $table.css("display");
114
+
115
+ }, 10);
116
+ return $this_th;
117
+ };
118
+
119
+ // Call on a sortable td to update its value in the sort. This should be the
120
+ // only mechanism used to update a cell's sort value. If your display value is
121
+ // different from your sort value, use jQuery's .text() or .html() to update
122
+ // the td contents, Assumes stupidtable has already been called for the table.
123
+ $.fn.updateSortVal = function(new_sort_val){
124
+ var $this_td = $(this);
125
+ if($this_td.is('[data-sort-value]')){
126
+ // For visual consistency with the .data cache
127
+ $this_td.attr('data-sort-value', new_sort_val);
128
+ }
129
+ $this_td.data("sort-value", new_sort_val);
130
+ return $this_td;
131
+ };
132
+
133
+
134
+ $.fn.stupidtable_build = function(){
135
+ return this.each(function() {
136
+ var $table = $(this);
137
+ var table_structure = [];
138
+ var trs = $table.children("tbody").children("tr");
139
+ trs.each(function(index,tr) {
140
+
141
+ // ====================================================================
142
+ // Transfer to using internal table structure
143
+ // ====================================================================
144
+ var ele = {
145
+ $tr: $(tr),
146
+ columns: [],
147
+ index: index
148
+ };
149
+
150
+ $(tr).children('td').each(function(idx, td){
151
+ var sort_val = $(td).data("sort-value");
152
+
153
+ // Store and read from the .data cache for display text only sorts
154
+ // instead of looking through the DOM every time
155
+ if(typeof(sort_val) === "undefined"){
156
+ var txt = $(td).text();
157
+ $(td).data('sort-value', txt);
158
+ sort_val = txt;
159
+ }
160
+ ele.columns.push(sort_val);
161
+ });
162
+ table_structure.push(ele);
163
+ });
164
+ $table.data('stupidsort_internaltable', table_structure);
165
+ });
166
+ };
167
+
168
+ // ====================================================================
169
+ // Private functions
170
+ // ====================================================================
171
+ var sortTable = function(sort_info){
172
+ var table_structure = sort_info.$table.data('stupidsort_internaltable');
173
+ var th_index = sort_info.th_index;
174
+ var $th = sort_info.$th;
175
+
176
+ var multicolumn_target_str = $th.data('sort-multicolumn');
177
+ var multicolumn_targets;
178
+ if(multicolumn_target_str){
179
+ multicolumn_targets = multicolumn_target_str.split(',');
180
+ }
181
+ else{
182
+ multicolumn_targets = [];
183
+ }
184
+ var multicolumn_th_targets = $.map(multicolumn_targets, function(identifier, i){
185
+ return get_th(sort_info.$table, identifier);
186
+ });
187
+
188
+ table_structure.sort(function(e1, e2){
189
+ var multicolumns = multicolumn_th_targets.slice(0); // shallow copy
190
+ var diff = sort_info.compare_fn(e1.columns[th_index], e2.columns[th_index]);
191
+ while(diff === 0 && multicolumns.length){
192
+ var multicolumn = multicolumns[0];
193
+ var datatype = multicolumn.$e.data("sort");
194
+ var multiCloumnSortMethod = sort_info.$table.data('sortFns')[datatype];
195
+ diff = multiCloumnSortMethod(e1.columns[multicolumn.index], e2.columns[multicolumn.index]);
196
+ multicolumns.shift();
197
+ }
198
+ // Sort by position in the table if values are the same. This enforces a
199
+ // stable sort across all browsers. See https://bugs.chromium.org/p/v8/issues/detail?id=90
200
+ if (diff === 0)
201
+ return e1.index - e2.index;
202
+ else
203
+ return diff;
204
+
205
+ });
206
+
207
+ if (sort_info.sort_dir != $.fn.stupidtable.dir.ASC){
208
+ table_structure.reverse();
111
209
  }
210
+ return table_structure;
211
+ };
212
+
213
+ var get_th = function($table, identifier){
214
+ // identifier can be a th id or a th index number;
215
+ var $table_ths = $table.find('th');
216
+ var index = parseInt(identifier, 10);
217
+ var $th;
218
+ if(!index && index !== 0){
219
+ $th = $table_ths.siblings('#' + identifier);
220
+ index = $table_ths.index($th);
221
+ }
222
+ else{
223
+ $th = $table_ths.eq(index);
224
+ }
225
+ return {index: index, $e: $th};
226
+ };
227
+
228
+ var getTableRowsFromTableStructure = function(table_structure, sort_info){
229
+ // Gather individual column for callbacks
230
+ var column = $.map(table_structure, function(ele, i){
231
+ return [[ele.columns[sort_info.th_index], ele.$tr, i]];
232
+ });
233
+
234
+ /* Side effect */
235
+ sort_info.column = column;
236
+
237
+ // Replace the content of tbody with the sorted rows. Strangely
238
+ // enough, .append accomplishes this for us.
239
+ return $.map(table_structure, function(ele) { return ele.$tr; });
240
+
241
+ };
242
+
243
+ var updateElementData = function(sort_info){
244
+ var $table = sort_info.$table;
245
+ var $this_th = sort_info.$th;
246
+ var sort_dir = $this_th.data('sort-dir');
247
+ var th_index = sort_info.th_index;
248
+
249
+
250
+ // Reset siblings
251
+ $table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
252
+ $this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
253
+ };
254
+
255
+ var calculateSortDir = function(force_direction, sort_info){
256
+ var sort_dir;
257
+ var $this_th = sort_info.$th;
258
+ var dir = $.fn.stupidtable.dir;
259
+
260
+ if(!!force_direction){
261
+ sort_dir = force_direction;
262
+ }
263
+ else{
264
+ sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC;
265
+ if ($this_th.data("sort-dir"))
266
+ sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
267
+ }
268
+ return sort_dir;
269
+ };
270
+
271
+ var calculateTHIndex = function(sort_info){
272
+ var th_index = 0;
273
+ var base_index = sort_info.$th.index();
274
+ sort_info.$th.parents("tr").find("th").slice(0, base_index).each(function() {
275
+ var cols = $(this).attr("colspan") || 1;
276
+ th_index += parseInt(cols,10);
277
+ });
278
+ return th_index;
112
279
  };
113
280
 
114
281
  })(jQuery);