sql-jarvis 1.8.0 → 1.9.6

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.
@@ -3,6 +3,7 @@
3
3
  *= require ./selectize.default
4
4
  *= require ./github
5
5
  *= require ./daterangepicker-bs3
6
+ *= require ./select2.min
6
7
  *= require_self
7
8
  */
8
9
 
@@ -0,0 +1 @@
1
+ .select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}
@@ -7,7 +7,7 @@ module Blazer
7
7
  skip_after_action(*filters, raise: false)
8
8
  skip_around_action(*filters, raise: false)
9
9
  else
10
- skip_action_callback *filters
10
+ skip_action_callback(*filters)
11
11
  end
12
12
 
13
13
  protect_from_forgery with: :exception
@@ -16,6 +16,10 @@ module Blazer
16
16
  http_basic_authenticate_with name: ENV["BLAZER_USERNAME"], password: ENV["BLAZER_PASSWORD"]
17
17
  end
18
18
 
19
+ if Blazer.settings["before_action"]
20
+ raise Blazer::Error, "The docs for protecting Blazer with a custom before_action had an incorrect example from August 2017 to June 2018. The example method had a boolean return value. However, you must render or redirect if a user is unauthorized rather than return a falsy value. Double check that your before_action works correctly for unauthorized users (if it worked when added, there should be no issue). Then, change before_action to before_action_method in config/blazer.yml."
21
+ end
22
+
19
23
  if Blazer.before_action
20
24
  before_action Blazer.before_action.to_sym
21
25
  end
@@ -53,7 +57,12 @@ module Blazer
53
57
  value = value.to_f
54
58
  end
55
59
  end
56
- statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
60
+ if value.is_a?(Array)
61
+ var_value = value.map{|v| ActiveRecord::Base.connection.quote(v)}.join(', ')
62
+ else
63
+ var_value = ActiveRecord::Base.connection.quote(value)
64
+ end
65
+ statement.gsub!("{#{var}}", var_value)
57
66
  end
58
67
  end
59
68
  end
@@ -1,6 +1,7 @@
1
1
  module Blazer
2
2
  class QueriesController < BaseController
3
3
  before_action :set_query, only: [:show, :edit, :update, :destroy, :refresh]
4
+ before_action :set_data_source, only: [:tables, :schema, :cancel]
4
5
 
5
6
  def home
6
7
  if params[:filter] == "dashboards"
@@ -34,6 +35,7 @@ module Blazer
34
35
  end
35
36
 
36
37
  def new
38
+ return render_forbidden unless Blazer::Query.creatable?(blazer_user)
37
39
  @query = Blazer::Query.new(
38
40
  data_source: params[:data_source],
39
41
  name: params[:name]
@@ -44,6 +46,7 @@ module Blazer
44
46
  end
45
47
 
46
48
  def create
49
+ return render_forbidden unless Blazer::Query.creatable?(blazer_user)
47
50
  @query = Blazer::Query.new(query_params)
48
51
  @query.creator = blazer_user if @query.respond_to?(:creator)
49
52
 
@@ -83,7 +86,10 @@ module Blazer
83
86
  data_source = @query.data_source if @query && @query.data_source
84
87
  @data_source = Blazer.data_sources[data_source]
85
88
 
86
- if @run_id
89
+ # ensure viewable
90
+ if !(@query || Query.new(data_source: @data_source.id)).viewable?(blazer_user)
91
+ render_forbidden
92
+ elsif @run_id
87
93
  @timestamp = blazer_params[:timestamp].to_i
88
94
 
89
95
  @result = @data_source.run_results(@run_id)
@@ -174,20 +180,28 @@ module Blazer
174
180
  end
175
181
 
176
182
  def tables
177
- render json: Blazer.data_sources[params[:data_source]].tables
183
+ render json: @data_source.tables
178
184
  end
179
185
 
180
186
  def schema
181
- @schema = Blazer.data_sources[params[:data_source]].schema
187
+ @schema = @data_source.schema
182
188
  end
183
189
 
184
190
  def cancel
185
- Blazer.data_sources[params[:data_source]].cancel(blazer_run_id)
191
+ @data_source.cancel(blazer_run_id)
186
192
  head :ok
187
193
  end
188
194
 
189
195
  private
190
196
 
197
+ def set_data_source
198
+ @data_source = Blazer.data_sources[params[:data_source]]
199
+
200
+ unless Query.new(data_source: @data_source.id).editable?(blazer_user)
201
+ render_forbidden
202
+ end
203
+ end
204
+
191
205
  def continue_run
192
206
  render json: {run_id: @run_id, timestamp: @timestamp}, status: :accepted
193
207
  end
@@ -198,7 +212,7 @@ module Blazer
198
212
  @first_row = @rows.first || []
199
213
  @column_types = []
200
214
  if @rows.any?
201
- @columns.each_with_index do |column, i|
215
+ @columns.each_with_index do |_, i|
202
216
  @column_types << (
203
217
  case @first_row[i]
204
218
  when Integer
@@ -248,13 +262,6 @@ module Blazer
248
262
  end
249
263
 
250
264
  def set_queries(limit = nil)
251
- @my_queries =
252
- if limit && blazer_user && !params[:filter] && Blazer.audit
253
- queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).where("created_at > ?", 30.days.ago).where("query_id IS NOT NULL").group(:query_id).order("count_all desc").count.keys)
254
- else
255
- []
256
- end
257
-
258
265
  @queries = Blazer::Query.named.select(:id, :name, :creator_id, :statement)
259
266
  @queries = @queries.includes(:creator) if Blazer.user_class
260
267
 
@@ -263,7 +270,6 @@ module Blazer
263
270
  elsif blazer_user && params[:filter] == "viewed" && Blazer.audit
264
271
  @queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
265
272
  else
266
- @queries = @queries.where("id NOT IN (?)", @my_queries.map(&:id)) if @my_queries.any?
267
273
  @queries = @queries.limit(limit) if limit
268
274
  @queries = @queries.order(:name)
269
275
  end
@@ -271,7 +277,7 @@ module Blazer
271
277
 
272
278
  @more = limit && @queries.size >= limit
273
279
 
274
- @queries = (@my_queries + @queries).select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
280
+ @queries = @queries.select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
275
281
 
276
282
  @queries =
277
283
  @queries.map do |q|
@@ -294,10 +300,18 @@ module Blazer
294
300
 
295
301
  def set_query
296
302
  @query = Blazer::Query.find(params[:id].to_s.split("-").first)
303
+
304
+ unless @query.viewable?(blazer_user)
305
+ render_forbidden
306
+ end
307
+ end
308
+
309
+ def render_forbidden
310
+ render plain: "Access denied", status: :forbidden
297
311
  end
298
312
 
299
313
  def query_params
300
- params.require(:query).permit(:name, :description, :statement, :data_source)
314
+ params.require(:query).permit!
301
315
  end
302
316
 
303
317
  def blazer_params
@@ -1,5 +1,7 @@
1
1
  module Blazer
2
2
  class Query < Record
3
+ serialize :assignee_ids, Array
4
+
3
5
  belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
4
6
  has_many :checks, dependent: :destroy
5
7
  has_many :dashboard_queries, dependent: :destroy
@@ -18,12 +20,25 @@ module Blazer
18
20
  name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip
19
21
  end
20
22
 
23
+ def viewable?(user)
24
+ if Blazer.query_viewable
25
+ Blazer.query_viewable.call(self, user)
26
+ else
27
+ true
28
+ end
29
+ end
30
+
21
31
  def editable?(user)
22
32
  editable = !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
33
+ editable &&= viewable?(user)
23
34
  editable &&= Blazer.query_editable.call(self, user) if Blazer.query_editable
24
35
  editable
25
36
  end
26
37
 
38
+ def self.creatable?(user)
39
+ Blazer.query_creatable.call(user) if Blazer.query_creatable
40
+ end
41
+
27
42
  def variables
28
43
  Blazer.extract_vars(statement)
29
44
  end
@@ -10,7 +10,7 @@
10
10
  <% @bind_vars.each_with_index do |var, i| %>
11
11
  <%= label_tag var, var %>
12
12
  <% if (data = @smart_vars[var]) %>
13
- <%= select_tag var, options_for_select([[nil, nil]] + data, selected: params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
13
+ <%= select_tag var, options_for_select([[nil, nil]] + data, selected: params[var]), style: "margin-right: 20px; width: 200px; display: none;", multiple: true %>
14
14
  <script>
15
15
  $("#<%= var %>").selectize({
16
16
  create: true
@@ -17,7 +17,7 @@
17
17
  <%= link_to "Back", :back %>
18
18
  </div>
19
19
  <a :href="dataSourcePath" target="_blank" style="margin-right: 10px;">Schema</a>
20
- <%= f.select :data_source, Blazer.data_sources.values.select { |ds| q = @query.dup; q.data_source = ds.id; q.editable?(blazer_user) }.map { |ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size == 1), style: "width: 140px;" %>
20
+ <%= f.select :data_source, Blazer.data_sources.values.select { |ds| q = @query.dup; q.data_source = ds.id; q.editable?(blazer_user) }.map { |ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size <= 1), style: "width: 140px;" %>
21
21
  <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
22
22
  <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
23
23
  </div>
@@ -34,6 +34,12 @@
34
34
  <%= f.label :description %>
35
35
  <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
36
36
  </div>
37
+ <%- if Blazer.assignees.any? -%>
38
+ <div class="form-group">
39
+ <%= f.label :assignees %>
40
+ <%= f.collection_select :assignee_ids, Blazer.assignees, :first, :last, {}, { placeholder: "Assignees", style: "height: 80px;", class: "form-control", multiple: true } %>
41
+ </div>
42
+ <%- end -%>
37
43
  <div class="text-right">
38
44
  <%= f.submit "For Enter Press", class: "hide" %>
39
45
  <% if @query.persisted? %>
@@ -66,7 +72,7 @@
66
72
 
67
73
  <script>
68
74
  <%= blazer_js_var "params", variable_params %>
69
- <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, v.preview_statement] }] %>
75
+ <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
70
76
 
71
77
  var app = new Vue({
72
78
  el: "#app",
@@ -173,7 +179,7 @@
173
179
  editor.focus()
174
180
  },
175
181
  adjustHeight: function() {
176
- // http://stackoverflow.com/questions/11584061/
182
+ // https://stackoverflow.com/questions/11584061/
177
183
  var editor = this.editor
178
184
  var lines = editor.getSession().getScreenLength()
179
185
  if (lines < 9) {
@@ -237,4 +243,8 @@
237
243
  this.showEditor()
238
244
  }
239
245
  })
246
+
247
+ $(document).ready(function() {
248
+ $('#query_assignee_ids').select2();
249
+ });
240
250
  </script>
@@ -111,6 +111,8 @@
111
111
  <div class="results-container">
112
112
  <% if @columns == ["QUERY PLAN"] %>
113
113
  <pre><code><%= @rows.map { |r| r[0] }.join("\n") %></code></pre>
114
+ <% elsif @columns == ["PLAN"] && @data_source.adapter == "druid" %>
115
+ <pre><code><%= @rows[0][0] %></code></pre>
114
116
  <% else %>
115
117
  <table class="table results-table" style="margin-bottom: 0;">
116
118
  <thead>
@@ -1,3 +1,5 @@
1
+ <% blazer_title "Database Schema" %>
2
+
1
3
  <% @schema.each do |table| %>
2
4
  <h4>
3
5
  <%= table[:table] %>
@@ -62,7 +62,7 @@
62
62
  </script>
63
63
  <% end %>
64
64
 
65
- <% if %w[sql presto drill bigquery athena].include?(Blazer.data_sources[@query.data_source].adapter) %>
65
+ <% unless %w(mongodb elasticsearch).include?(Blazer.data_sources[@query.data_source].adapter) %>
66
66
  <script>
67
67
  // do not highlight really long queries
68
68
  // this can lead to performance issues
data/blazer.gemspec CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "rails", ">= 4"
20
+ spec.add_dependency "railties", ">= 4"
21
+ spec.add_dependency "activerecord", ">= 4"
21
22
  spec.add_dependency "chartkick"
22
23
  spec.add_dependency "safely_block", ">= 0.1.1"
23
24
 
data/lib/blazer.rb CHANGED
@@ -9,11 +9,14 @@ require "blazer/run_statement"
9
9
  require "blazer/adapters/base_adapter"
10
10
  require "blazer/adapters/athena_adapter"
11
11
  require "blazer/adapters/bigquery_adapter"
12
+ require "blazer/adapters/cassandra_adapter"
12
13
  require "blazer/adapters/drill_adapter"
14
+ require "blazer/adapters/druid_adapter"
13
15
  require "blazer/adapters/elasticsearch_adapter"
14
16
  require "blazer/adapters/mongodb_adapter"
15
17
  require "blazer/adapters/presto_adapter"
16
18
  require "blazer/adapters/sql_adapter"
19
+ require "blazer/adapters/snowflake_adapter"
17
20
  require "blazer/engine"
18
21
 
19
22
  module Blazer
@@ -24,23 +27,27 @@ module Blazer
24
27
  attr_accessor :audit
25
28
  attr_reader :time_zone
26
29
  attr_accessor :user_name
27
- attr_accessor :user_class
28
- attr_accessor :user_method
30
+ attr_writer :user_class
31
+ attr_writer :user_method
29
32
  attr_accessor :before_action
30
33
  attr_accessor :from_email
31
34
  attr_accessor :cache
32
35
  attr_accessor :transform_statement
33
36
  attr_accessor :check_schedules
34
37
  attr_accessor :mapbox_access_token
38
+ attr_accessor :assignees
35
39
  attr_accessor :anomaly_checks
36
40
  attr_accessor :async
37
41
  attr_accessor :images
42
+ attr_accessor :query_viewable
38
43
  attr_accessor :query_editable
44
+ attr_accessor :query_creatable
39
45
  end
40
46
  self.audit = true
41
47
  self.user_name = :name
42
48
  self.check_schedules = ["5 minutes", "1 hour", "1 day"]
43
49
  self.mapbox_access_token = nil
50
+ self.assignees = []
44
51
  self.anomaly_checks = false
45
52
  self.async = false
46
53
  self.images = false
@@ -61,6 +68,23 @@ module Blazer
61
68
  @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
62
69
  end
63
70
 
71
+ def self.user_class
72
+ if !defined?(@user_class)
73
+ @user_class = settings.key?("user_class") ? settings["user_class"] : (User.name rescue nil)
74
+ end
75
+ @user_class
76
+ end
77
+
78
+ def self.user_method
79
+ if !defined?(@user_method)
80
+ @user_method = settings["user_method"]
81
+ if user_class
82
+ @user_method ||= "current_#{user_class.to_s.downcase.singularize}"
83
+ end
84
+ end
85
+ @user_method
86
+ end
87
+
64
88
  def self.settings
65
89
  @settings ||= begin
66
90
  path = Rails.root.join("config", "blazer.yml").to_s
@@ -107,8 +131,6 @@ module Blazer
107
131
  end
108
132
 
109
133
  def self.run_check(check)
110
- rows = nil
111
- error = nil
112
134
  tries = 1
113
135
 
114
136
  ActiveSupport::Notifications.instrument("run_check.blazer", check_id: check.id, query_id: check.query.id, state_was: check.state) do |instrument|
@@ -176,10 +198,13 @@ module Blazer
176
198
  end
177
199
  end
178
200
 
179
- Blazer.register_adapter "drill", Blazer::Adapters::DrillAdapter
180
- Blazer.register_adapter "bigquery", Blazer::Adapters::BigQueryAdapter
181
201
  Blazer.register_adapter "athena", Blazer::Adapters::AthenaAdapter
202
+ Blazer.register_adapter "bigquery", Blazer::Adapters::BigQueryAdapter
203
+ Blazer.register_adapter "cassandra", Blazer::Adapters::CassandraAdapter
204
+ Blazer.register_adapter "drill", Blazer::Adapters::DrillAdapter
205
+ Blazer.register_adapter "druid", Blazer::Adapters::DruidAdapter
182
206
  Blazer.register_adapter "elasticsearch", Blazer::Adapters::ElasticsearchAdapter
183
- Blazer.register_adapter "mongodb", Blazer::Adapters::MongodbAdapter
184
207
  Blazer.register_adapter "presto", Blazer::Adapters::PrestoAdapter
208
+ Blazer.register_adapter "mongodb", Blazer::Adapters::MongodbAdapter
185
209
  Blazer.register_adapter "sql", Blazer::Adapters::SqlAdapter
210
+ Blazer.register_adapter "snowflake", Blazer::Adapters::SnowflakeAdapter
@@ -7,10 +7,11 @@ module Blazer
7
7
  error = nil
8
8
 
9
9
  begin
10
- options = {}
11
- options[:timeout] = data_source.timeout.to_i * 1000 if data_source.timeout
12
- results = bigquery.query(statement, options) # ms
13
- if results.complete?
10
+ results = bigquery.query(statement)
11
+
12
+ # complete? was removed in google-cloud-bigquery 0.29.0
13
+ # code is for backward compatibility
14
+ if !results.respond_to?(:complete?) || results.complete?
14
15
  columns = results.first.keys.map(&:to_s) if results.size > 0
15
16
  rows = results.map(&:values)
16
17
  else
@@ -0,0 +1,59 @@
1
+ module Blazer
2
+ module Adapters
3
+ class CassandraAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ response = session.execute("#{statement} /*#{comment}*/")
11
+ rows = response.map { |r| r.values }
12
+ columns = rows.any? ? response.first.keys : []
13
+ rescue => e
14
+ error = e.message
15
+ end
16
+
17
+ [columns, rows, error]
18
+ end
19
+
20
+ def tables
21
+ session.execute("SELECT table_name FROM system_schema.tables WHERE keyspace_name = '#{keyspace}'").map { |r| r["table_name"] }
22
+ end
23
+
24
+ def schema
25
+ result = session.execute("SELECT keyspace_name, table_name, column_name, type, position FROM system_schema.columns WHERE keyspace_name = '#{keyspace}'")
26
+ result.map(&:values).group_by { |r| [r[0], r[1]] }.map { |k, vs| {schema: k[0], table: k[1], columns: vs.sort_by { |v| v[2] }.map { |v| {name: v[2], data_type: v[3]} }} }
27
+ end
28
+
29
+ def preview_statement
30
+ "SELECT * FROM {table} LIMIT 10"
31
+ end
32
+
33
+ private
34
+
35
+ def cluster
36
+ @cluster ||= begin
37
+ require "cassandra"
38
+ options = {hosts: [uri.host]}
39
+ options[:port] = uri.port if uri.port
40
+ options[:username] = uri.user if uri.user
41
+ options[:password] = uri.password if uri.password
42
+ ::Cassandra.cluster(options)
43
+ end
44
+ end
45
+
46
+ def session
47
+ @session ||= cluster.connect(keyspace)
48
+ end
49
+
50
+ def uri
51
+ @uri ||= URI.parse(data_source.settings["url"])
52
+ end
53
+
54
+ def keyspace
55
+ @keyspace ||= uri.path[1..-1]
56
+ end
57
+ end
58
+ end
59
+ end