queries_controller.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. module Blazer
  2. class QueriesController < BaseController
  3. before_action :set_query, only: [:show, :edit, :update, :destroy, :refresh]
  4. before_action :set_data_source, only: [:tables, :schema, :cancel]
  5. def home
  6. if params[:filter] == "dashboards"
  7. @queries = []
  8. else
  9. set_queries(1000)
  10. end
  11. if params[:filter] && params[:filter] != "dashboards"
  12. @dashboards = [] # TODO show my dashboards
  13. else
  14. @dashboards = Blazer::Dashboard.order(:name)
  15. @dashboards = @dashboards.includes(:creator) if Blazer.user_class
  16. end
  17. @dashboards =
  18. @dashboards.map do |d|
  19. {
  20. id: d.id,
  21. name: d.name,
  22. creator: blazer_user && d.try(:creator) == blazer_user ? "You" : d.try(:creator).try(Blazer.user_name),
  23. to_param: d.to_param,
  24. dashboard: true
  25. }
  26. end
  27. end
  28. def index
  29. set_queries
  30. render json: @queries
  31. end
  32. def new
  33. @query = Blazer::Query.new(
  34. data_source: params[:data_source],
  35. name: params[:name]
  36. )
  37. if params[:fork_query_id]
  38. @query.statement ||= Blazer::Query.find(params[:fork_query_id]).try(:statement)
  39. end
  40. end
  41. def create
  42. @query = Blazer::Query.new(query_params)
  43. @query.creator = blazer_user if @query.respond_to?(:creator)
  44. if @query.save
  45. redirect_to query_path(@query, variable_params)
  46. else
  47. render_errors @query
  48. end
  49. end
  50. def show
  51. @statement = @query.statement.dup
  52. process_vars(@statement, @query.data_source)
  53. @smart_vars = {}
  54. @sql_errors = []
  55. data_source = Blazer.data_sources[@query.data_source]
  56. @bind_vars.each do |var|
  57. smart_var, error = parse_smart_variables(var, data_source)
  58. @smart_vars[var] = smart_var if smart_var
  59. @sql_errors << error if error
  60. end
  61. Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
  62. end
  63. def edit
  64. end
  65. def run
  66. @statement = params[:statement]
  67. data_source = params[:data_source]
  68. process_vars(@statement, data_source)
  69. @only_chart = params[:only_chart]
  70. @run_id = blazer_params[:run_id]
  71. @query = Query.find_by(id: params[:query_id]) if params[:query_id]
  72. data_source = @query.data_source if @query && @query.data_source
  73. @data_source = Blazer.data_sources[data_source]
  74. # ensure viewable
  75. if !(@query || Query.new(data_source: @data_source.id)).viewable?(blazer_user)
  76. render_forbidden
  77. elsif @run_id
  78. @timestamp = blazer_params[:timestamp].to_i
  79. @result = @data_source.run_results(@run_id)
  80. @success = !@result.nil?
  81. if @success
  82. @data_source.delete_results(@run_id)
  83. @columns = @result.columns
  84. @rows = @result.rows
  85. @error = @result.error
  86. @just_cached = !@result.error && @result.cached_at.present?
  87. @cached_at = nil
  88. params[:data_source] = nil
  89. render_run
  90. elsif Time.now > Time.at(@timestamp + (@data_source.timeout || 600).to_i + 5)
  91. # query lost
  92. Rails.logger.info "[blazer lost query] #{@run_id}"
  93. @error = "We lost your query :("
  94. @rows = []
  95. @columns = []
  96. render_run
  97. else
  98. continue_run
  99. end
  100. elsif @success
  101. @run_id = blazer_run_id
  102. options = {user: blazer_user, query: @query, refresh_cache: params[:check], run_id: @run_id, async: Blazer.async}
  103. if Blazer.async && request.format.symbol != :csv
  104. result = []
  105. Blazer::RunStatementJob.perform_async(result, @data_source, @statement, options)
  106. wait_start = Time.now
  107. loop do
  108. sleep(0.02)
  109. break if result.any? || Time.now - wait_start > 3
  110. end
  111. @result = result.first
  112. else
  113. @result = Blazer::RunStatement.new.perform(@data_source, @statement, options)
  114. end
  115. if @result
  116. @data_source.delete_results(@run_id) if @run_id
  117. @columns = @result.columns
  118. @rows = @result.rows
  119. @error = @result.error
  120. @cached_at = @result.cached_at
  121. @just_cached = @result.just_cached
  122. render_run
  123. else
  124. @timestamp = Time.now.to_i
  125. continue_run
  126. end
  127. else
  128. render layout: false
  129. end
  130. end
  131. def refresh
  132. data_source = Blazer.data_sources[@query.data_source]
  133. @statement = @query.statement.dup
  134. process_vars(@statement, @query.data_source)
  135. Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
  136. data_source.clear_cache(@statement)
  137. redirect_to query_path(@query, variable_params)
  138. end
  139. def update
  140. if params[:commit] == "Fork"
  141. @query = Blazer::Query.new
  142. @query.creator = blazer_user if @query.respond_to?(:creator)
  143. end
  144. unless @query.editable?(blazer_user)
  145. @query.errors.add(:base, "Sorry, permission denied")
  146. end
  147. if @query.errors.empty? && @query.update(query_params)
  148. redirect_to query_path(@query, variable_params)
  149. else
  150. render_errors @query
  151. end
  152. end
  153. def destroy
  154. @query.destroy if @query.editable?(blazer_user)
  155. redirect_to root_url
  156. end
  157. def tables
  158. render json: @data_source.tables
  159. end
  160. def schema
  161. @schema = @data_source.schema
  162. end
  163. def cancel
  164. @data_source.cancel(blazer_run_id)
  165. head :ok
  166. end
  167. private
  168. def set_data_source
  169. @data_source = Blazer.data_sources[params[:data_source]]
  170. unless Query.new(data_source: @data_source.id).editable?(blazer_user)
  171. render_forbidden
  172. end
  173. end
  174. def continue_run
  175. render json: {run_id: @run_id, timestamp: @timestamp}, status: :accepted
  176. end
  177. def render_run
  178. @checks = @query ? @query.checks.order(:id) : []
  179. @first_row = @rows.first || []
  180. @column_types = []
  181. if @rows.any?
  182. @columns.each_with_index do |column, i|
  183. @column_types << (
  184. case @first_row[i]
  185. when Integer
  186. "int"
  187. when Float, BigDecimal
  188. "float"
  189. else
  190. "string-ins"
  191. end
  192. )
  193. end
  194. end
  195. @filename = @query.name.parameterize if @query
  196. @min_width_types = @columns.each_with_index.select { |c, i| @first_row[i].is_a?(Time) || @first_row[i].is_a?(String) || @data_source.smart_columns[c] }.map(&:last)
  197. @boom = @result.boom if @result
  198. @linked_columns = @data_source.linked_columns
  199. @markers = []
  200. [["latitude", "longitude"], ["lat", "lon"], ["lat", "lng"]].each do |keys|
  201. lat_index = @columns.index(keys.first)
  202. lon_index = @columns.index(keys.last)
  203. if lat_index && lon_index
  204. @markers =
  205. @rows.select do |r|
  206. r[lat_index] && r[lon_index]
  207. end.map do |r|
  208. {
  209. title: r.each_with_index.map{ |v, i| i == lat_index || i == lon_index ? nil : "<strong>#{@columns[i]}:</strong> #{v}" }.compact.join("<br />").truncate(140),
  210. latitude: r[lat_index],
  211. longitude: r[lon_index]
  212. }
  213. end
  214. end
  215. end
  216. respond_to do |format|
  217. format.html do
  218. render layout: false
  219. end
  220. format.csv do
  221. send_data csv_data(@columns, @rows, @data_source), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
  222. end
  223. end
  224. end
  225. def set_queries(limit = nil)
  226. @queries = Blazer::Query.named.select(:id, :name, :creator_id, :statement)
  227. @queries = @queries.includes(:creator) if Blazer.user_class
  228. if blazer_user && params[:filter] == "mine"
  229. @queries = @queries.where(creator_id: blazer_user.id).reorder(updated_at: :desc)
  230. elsif blazer_user && params[:filter] == "viewed" && Blazer.audit
  231. @queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
  232. else
  233. @queries = @queries.limit(limit) if limit
  234. @queries = @queries.order(:name)
  235. end
  236. @queries = @queries.to_a
  237. @more = limit && @queries.size >= limit
  238. @queries = @queries.select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
  239. @queries =
  240. @queries.map do |q|
  241. {
  242. id: q.id,
  243. name: q.name,
  244. creator: blazer_user && q.try(:creator) == blazer_user ? "You" : q.try(:creator).try(Blazer.user_name),
  245. vars: q.variables.join(", "),
  246. to_param: q.to_param
  247. }
  248. end
  249. end
  250. def queries_by_ids(favorite_query_ids)
  251. queries = Blazer::Query.named.where(id: favorite_query_ids)
  252. queries = queries.includes(:creator) if Blazer.user_class
  253. queries = queries.index_by(&:id)
  254. favorite_query_ids.map { |query_id| queries[query_id] }.compact
  255. end
  256. def set_query
  257. @query = Blazer::Query.find(params[:id].to_s.split("-").first)
  258. unless @query.viewable?(blazer_user)
  259. render_forbidden
  260. end
  261. end
  262. def render_forbidden
  263. render plain: "Access denied", status: :forbidden
  264. end
  265. def query_params
  266. params.require(:query).permit(:name, :description, :statement, :data_source)
  267. end
  268. def blazer_params
  269. params[:blazer] || {}
  270. end
  271. def csv_data(columns, rows, data_source)
  272. CSV.generate do |csv|
  273. csv << columns
  274. rows.each do |row|
  275. csv << row.each_with_index.map { |v, i| v.is_a?(Time) ? blazer_time_value(data_source, columns[i], v) : v }
  276. end
  277. end
  278. end
  279. def blazer_time_value(data_source, k, v)
  280. data_source.local_time_suffix.any? { |s| k.ends_with?(s) } ? v.to_s.sub(" UTC", "") : v.in_time_zone(Blazer.time_zone)
  281. end
  282. helper_method :blazer_time_value
  283. def blazer_run_id
  284. params[:run_id].to_s.gsub(/[^a-z0-9\-]/i, "")
  285. end
  286. end
  287. end