blazer 3.3.0 → 3.5.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +23 -33
  5. data/app/assets/javascripts/blazer/Sortable.js +826 -1162
  6. data/app/assets/javascripts/blazer/ace/ace.js +15775 -13360
  7. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1411 -668
  8. data/app/assets/javascripts/blazer/ace/mode-sql.js +12 -6
  9. data/app/assets/javascripts/blazer/ace/theme-twilight.js +3 -3
  10. data/app/assets/javascripts/blazer/application.js +94 -52
  11. data/app/assets/javascripts/blazer/chart.umd.js +6 -6
  12. data/app/assets/javascripts/blazer/daterangepicker.js +193 -199
  13. data/app/assets/javascripts/blazer/highlight.min.js +235 -373
  14. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +116 -82
  15. data/app/assets/javascripts/blazer/moment.js +368 -365
  16. data/app/assets/javascripts/blazer/queries.js +68 -58
  17. data/app/assets/javascripts/blazer/routes.js +11 -11
  18. data/app/assets/javascripts/blazer/tablesort.js +42 -0
  19. data/app/assets/javascripts/blazer/tom-select.base.js +4175 -0
  20. data/app/assets/stylesheets/blazer/application.css +114 -5
  21. data/app/assets/stylesheets/blazer/tom-select.css +502 -0
  22. data/app/controllers/blazer/base_controller.rb +3 -3
  23. data/app/controllers/blazer/checks_controller.rb +5 -2
  24. data/app/controllers/blazer/queries_controller.rb +3 -1
  25. data/app/helpers/blazer/base_helper.rb +1 -1
  26. data/app/models/blazer/check.rb +35 -57
  27. data/app/views/blazer/_nav.html.erb +1 -1
  28. data/app/views/blazer/_variables.html.erb +57 -42
  29. data/app/views/blazer/checks/_form.html.erb +20 -16
  30. data/app/views/blazer/checks/index.html.erb +17 -20
  31. data/app/views/blazer/dashboards/_form.html.erb +63 -48
  32. data/app/views/blazer/dashboards/show.html.erb +23 -16
  33. data/app/views/blazer/queries/_caching.html.erb +1 -1
  34. data/app/views/blazer/queries/_chart.html.erb +8 -0
  35. data/app/views/blazer/queries/_form.html.erb +153 -180
  36. data/app/views/blazer/queries/_map.html.erb +8 -0
  37. data/app/views/blazer/queries/docs.html.erb +6 -6
  38. data/app/views/blazer/queries/edit.html.erb +1 -0
  39. data/app/views/blazer/queries/home.html.erb +101 -102
  40. data/app/views/blazer/queries/new.html.erb +1 -0
  41. data/app/views/blazer/queries/run.html.erb +23 -34
  42. data/app/views/blazer/queries/schema.html.erb +20 -20
  43. data/app/views/blazer/queries/show.html.erb +19 -12
  44. data/app/views/blazer/uploads/index.html.erb +10 -10
  45. data/app/views/layouts/blazer/application.html.erb +3 -3
  46. data/lib/blazer/adapters/athena_adapter.rb +12 -6
  47. data/lib/blazer/adapters/clickhouse_adapter.rb +136 -0
  48. data/lib/blazer/adapters/druid_adapter.rb +0 -2
  49. data/lib/blazer/adapters/snowflake2_adapter.rb +198 -0
  50. data/lib/blazer/adapters/soda_adapter.rb +0 -2
  51. data/lib/blazer/adapters/sql_adapter.rb +5 -1
  52. data/lib/blazer/adapters.rb +2 -0
  53. data/lib/blazer/check_types.rb +20 -0
  54. data/lib/blazer/data_source.rb +1 -1
  55. data/lib/blazer/email_notifier.rb +49 -0
  56. data/lib/blazer/result.rb +15 -0
  57. data/lib/blazer/slack_notifier.rb +39 -7
  58. data/lib/blazer/version.rb +1 -1
  59. data/lib/blazer.rb +32 -26
  60. data/lib/generators/blazer/templates/config.yml.tt +0 -2
  61. data/licenses/LICENSE-chart.js.txt +1 -1
  62. metadata +20 -39
  63. data/app/assets/javascripts/blazer/bootstrap.js +0 -2580
  64. data/app/assets/javascripts/blazer/jquery.js +0 -10872
  65. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +0 -325
  66. data/app/assets/javascripts/blazer/rails-ujs.js +0 -746
  67. data/app/assets/javascripts/blazer/selectize.js +0 -3891
  68. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +0 -13
  69. data/app/assets/javascripts/blazer/stupidtable.js +0 -281
  70. data/app/assets/javascripts/blazer/vue.global.prod.js +0 -1
  71. data/app/assets/stylesheets/blazer/selectize.css +0 -403
  72. data/licenses/LICENSE-jquery.txt +0 -20
  73. data/licenses/LICENSE-rails-ujs.txt +0 -20
  74. data/licenses/LICENSE-stickytableheaders.txt +0 -20
  75. data/licenses/LICENSE-stupidtable.txt +0 -19
  76. data/licenses/LICENSE-vue.txt +0 -21
  77. /data/licenses/{LICENSE-selectize.txt → LICENSE-tom-select.txt} +0 -0
@@ -1,13 +1,13 @@
1
- var pendingQueries = []
2
- var runningQueries = []
3
- var maxQueries = 3
1
+ let pendingQueries = []
2
+ const runningQueries = []
3
+ const maxQueries = 3
4
4
 
5
5
  function runQuery(data, success, error) {
6
6
  if (!data.data_source) {
7
7
  throw new Error("Data source is required to cancel queries")
8
8
  }
9
9
  data.run_id = uuid()
10
- var query = {
10
+ const query = {
11
11
  data: data,
12
12
  success: success,
13
13
  error: error,
@@ -22,7 +22,7 @@ function runQuery(data, success, error) {
22
22
 
23
23
  function runNext() {
24
24
  if (runningQueries.length < maxQueries) {
25
- var query = pendingQueries.shift()
25
+ const query = pendingQueries.shift()
26
26
  if (query) {
27
27
  runningQueries.push(query)
28
28
  runQueryHelper(query)
@@ -32,99 +32,109 @@ function runNext() {
32
32
  }
33
33
 
34
34
  function runQueryHelper(query) {
35
- var xhr = $.ajax({
36
- url: Routes.run_queries_path(),
37
- method: "POST",
38
- data: query.data,
39
- dataType: "html"
40
- }).done( function (d) {
41
- if (d[0] == "{") {
42
- var response = $.parseJSON(d)
43
- query.data.blazer = response
44
- setTimeout( function () {
35
+ const formdata = createFormData(csrfProtect(query.data))
36
+ const controller = new AbortController()
37
+ fetch(Routes.run_queries_path(), {method: "POST", body: formdata, signal: controller.signal})
38
+ .then(function (response) {
39
+ if (!response.ok) {
40
+ throw new Error(response.statusText)
41
+ }
42
+ return response.text()
43
+ })
44
+ .then(function (text) {
45
+ if (text[0] == "{") {
46
+ query.data.blazer = JSON.parse(text)
47
+ setTimeout(function () {
48
+ if (!query.canceled) {
49
+ runQueryHelper(query)
50
+ }
51
+ }, 1000)
52
+ } else {
45
53
  if (!query.canceled) {
46
- runQueryHelper(query)
54
+ query.success(text)
47
55
  }
48
- }, 1000)
49
- } else {
50
- if (!query.canceled) {
51
- query.success(d)
56
+ queryComplete(query)
52
57
  }
53
- queryComplete(query)
54
- }
55
- }).fail( function(jqXHR, textStatus, errorThrown) {
56
- // check jqXHR.status instead of query.canceled
57
- // so it works for page navigation with Firefox and Safari
58
- if (jqXHR.status === 0) {
59
- cancelServerQuery(query)
60
- } else {
61
- var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message
62
- if (!message) {
63
- message = "An error occurred"
58
+ })
59
+ .catch(function (error) {
60
+ if (error.name == "AbortError") {
61
+ cancelServerQuery(query)
62
+ } else {
63
+ query.error(error.message)
64
64
  }
65
- query.error(message)
66
- }
67
- queryComplete(query)
68
- })
69
- query.xhr = xhr
70
- return xhr
65
+ queryComplete(query)
66
+ })
67
+ query.controller = controller
71
68
  }
72
69
 
73
70
  function queryComplete(query) {
74
- var index = runningQueries.indexOf(query)
71
+ const index = runningQueries.indexOf(query)
75
72
  runningQueries.splice(index, 1)
76
73
  runNext()
77
74
  }
78
75
 
79
76
  function uuid() {
80
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
81
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8)
77
+ if (window.crypto && window.crypto.randomUUID) {
78
+ return window.crypto.randomUUID()
79
+ }
80
+
81
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
82
+ const r = (Math.random() * 16) | 0,
83
+ v = c == "x" ? r : (r & 0x3) | 0x8
82
84
  return v.toString(16)
83
85
  })
84
86
  }
85
87
 
86
88
  function cancelAllQueries() {
87
89
  pendingQueries = []
88
- for (var i = 0; i < runningQueries.length; i++) {
89
- cancelQuery(runningQueries[i])
90
+ for (const query of runningQueries) {
91
+ cancelQuery(query)
90
92
  }
91
93
  }
92
94
 
93
95
  // needed for Chrome
94
96
  // queries are canceled before unload with Firefox and Safari
95
- $(window).on("unload", cancelAllQueries)
97
+ window.addEventListener("beforeunload", cancelAllQueries)
96
98
 
97
99
  function cancelQuery(query) {
98
100
  query.canceled = true
99
- if (query.xhr) {
100
- query.xhr.abort()
101
+ if (query.controller) {
102
+ query.controller.abort()
101
103
  }
102
104
  }
103
105
 
106
+ function createFormData(params) {
107
+ const formdata = new FormData()
108
+ for (const [key, value] of Object.entries(params)) {
109
+ if (typeof value === "object") {
110
+ // handle single level of nesting
111
+ for (const [k, v] of Object.entries(value)) {
112
+ formdata.append(`${key}[${k}]`, v)
113
+ }
114
+ } else {
115
+ formdata.append(key, value)
116
+ }
117
+ }
118
+ return formdata
119
+ }
120
+
104
121
  function cancelServerQuery(query) {
105
122
  // tell server
106
- var path = Routes.cancel_queries_path()
107
- var data = {run_id: query.run_id, data_source: query.data_source}
123
+ const path = Routes.cancel_queries_path()
124
+ const data = {run_id: query.run_id, data_source: query.data_source}
125
+ const formdata = createFormData(csrfProtect(data))
108
126
  if (navigator.sendBeacon) {
109
127
  // use FormData over Blob and URLSearchParams for maximum compatibility
110
128
  // Blob works with Chrome 81+ and URLSearchParams works with Chrome 88+
111
- var formdata = new FormData()
112
- var params = csrfProtect(data)
113
- for (var key in params) {
114
- if (Object.prototype.hasOwnProperty.call(params, key)) {
115
- formdata.append(key, params[key])
116
- }
117
- }
118
129
  navigator.sendBeacon(path, formdata)
119
130
  } else {
120
- // TODO make sync
121
- $.post(path, data)
131
+ fetch(path, {method: "POST", body: formdata})
122
132
  }
123
133
  }
124
134
 
125
135
  function csrfProtect(payload) {
126
- var param = $("meta[name=csrf-param]").attr("content")
127
- var token = $("meta[name=csrf-token]").attr("content")
136
+ const param = document.querySelector("meta[name=csrf-param]")?.getAttribute("content")
137
+ const token = document.querySelector("meta[name=csrf-token]")?.getAttribute("content")
128
138
  if (param && token) payload[param] = token
129
139
  return payload
130
140
  }
@@ -1,26 +1,26 @@
1
1
  var Routes = {
2
- run_queries_path: function() {
2
+ run_queries_path: function () {
3
3
  return rootPath + "queries/run"
4
4
  },
5
- cancel_queries_path: function() {
5
+ cancel_queries_path: function () {
6
6
  return rootPath + "queries/cancel"
7
7
  },
8
- schema_queries_path: function(params) {
9
- return rootPath + "queries/schema?" + $.param(params)
8
+ schema_queries_path: function (params) {
9
+ return rootPath + "queries/schema?" + pathParams(params)
10
10
  },
11
- docs_queries_path: function(params) {
12
- return rootPath + "queries/docs?" + $.param(params)
11
+ docs_queries_path: function (params) {
12
+ return rootPath + "queries/docs?" + pathParams(params)
13
13
  },
14
- tables_queries_path: function(params) {
15
- return rootPath + "queries/tables?" + $.param(params)
14
+ tables_queries_path: function (params) {
15
+ return rootPath + "queries/tables?" + pathParams(params)
16
16
  },
17
- queries_path: function() {
17
+ queries_path: function () {
18
18
  return rootPath + "queries"
19
19
  },
20
- query_path: function(id) {
20
+ query_path: function (id) {
21
21
  return rootPath + "queries/" + id
22
22
  },
23
- dashboard_path: function(id) {
23
+ dashboard_path: function (id) {
24
24
  return rootPath + "dashboards/" + id
25
25
  }
26
26
  }
@@ -0,0 +1,42 @@
1
+ function removeDelimiters(string) {
2
+ return string.replace(/,/g, "")
3
+ }
4
+
5
+ function compareInt(a, b) {
6
+ return parseInt(removeDelimiters(a), 10) - parseInt(removeDelimiters(b), 10)
7
+ }
8
+
9
+ function compareFloat(a, b) {
10
+ return parseFloat(removeDelimiters(a)) - parseFloat(removeDelimiters(b))
11
+ }
12
+
13
+ function compareString(a, b) {
14
+ return a.localeCompare(b, undefined, {sensitivity: "base"})
15
+ }
16
+
17
+ function tablesort(element) {
18
+ let lastIndex = -1
19
+ let reverse = false
20
+
21
+ element.addEventListener("click", function (e) {
22
+ const target = e.target.closest("th")
23
+ if (target) {
24
+ const columnIndex = Array.from(element.querySelectorAll("thead th")).indexOf(target)
25
+ const sortType = target.getAttribute("data-sort")
26
+ const compareFn = sortType == "int" ? compareInt : (sortType == "float" ? compareFloat : compareString)
27
+ reverse = columnIndex != lastIndex ? false : !reverse
28
+
29
+ const tbody = element.querySelector("tbody")
30
+ const rows = Array.from(tbody.querySelectorAll("tr"))
31
+ rows.sort(function (a, b) {
32
+ const av = a.children[columnIndex].textContent
33
+ const bv = b.children[columnIndex].textContent
34
+ const r = compareFn(av, bv)
35
+ return reverse ? -r : r
36
+ })
37
+ tbody.replaceChildren(...rows)
38
+
39
+ lastIndex = columnIndex
40
+ }
41
+ })
42
+ }