_form.html.erb 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <% if @query.errors.any? %>
  2. <div class="alert alert-danger"><%= @query.errors.full_messages.first %></div>
  3. <% end %>
  4. <div id="app" v-cloak>
  5. <%= form_for @query, url: (@query.persisted? ? query_path(@query, variable_params) : queries_path(variable_params)), html: {autocomplete: "off"} do |f| %>
  6. <div class="row">
  7. <div id="statement-box" class="col-xs-8">
  8. <div class= "form-group">
  9. <%= f.hidden_field :statement %>
  10. <div id="editor-container">
  11. <div id="editor" :style="{ height: editorHeight }"><%= @query.statement %></div>
  12. </div>
  13. </div>
  14. <div class="form-group text-right">
  15. <div class="pull-left" style="margin-top: 9px;">
  16. <%= link_to "Back", :back %>
  17. </div>
  18. <a :href="dataSourcePath" target="_blank" style="margin-right: 10px;">Schema</a>
  19. <%= 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. <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
  21. <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
  22. </div>
  23. <a v-on:click="run" v-if="!running" class="btn btn-info" style="vertical-align: top; width: 70px;">Run</a>
  24. <a v-on:click="cancel" v-if="running" class="btn btn-danger" style="vertical-align: top; width: 70px;">Cancel</a>
  25. </div>
  26. </div>
  27. <div class="col-xs-4">
  28. <div class="form-group">
  29. <%= f.label :name %>
  30. <%= f.text_field :name, class: "form-control" %>
  31. </div>
  32. <div class="form-group">
  33. <%= f.label :description %>
  34. <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
  35. </div>
  36. <div class="text-right">
  37. <%= f.submit "For Enter Press", class: "hide" %>
  38. <% if @query.persisted? %>
  39. <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
  40. <%= f.submit "Fork", class: "btn btn-info" %>
  41. <% end %>
  42. <%= f.submit @query.persisted? ? "Update" : "Create", class: "btn btn-success" %>
  43. </div>
  44. <% if @query.persisted? %>
  45. <% dashboards_count = @query.dashboards.count %>
  46. <% checks_count = @query.checks.count %>
  47. <% words = [] %>
  48. <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
  49. <% words << pluralize(checks_count, "check") if checks_count > 0 %>
  50. <% if words.any? %>
  51. <div class="alert alert-info" style="margin-top: 10px; padding: 8px 12px;">
  52. Part of <%= words.to_sentence %>. Be careful when editing.
  53. </div>
  54. <% end %>
  55. <% end %>
  56. </div>
  57. </div>
  58. <% end %>
  59. <div id="results">
  60. <p class="text-muted" v-if="running">Loading...</p>
  61. <div id="results-html" v-if="!running" :class="{ 'query-error': error }"></div>
  62. </div>
  63. </div>
  64. <script>
  65. <%= blazer_js_var "params", variable_params %>
  66. <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
  67. var app = new Vue({
  68. el: "#app",
  69. data: {
  70. running: false,
  71. results: "",
  72. error: false,
  73. dataSource: "",
  74. selectize: null,
  75. editorHeight: "180px"
  76. },
  77. computed: {
  78. dataSourcePath: function() {
  79. return Routes.schema_queries_path({data_source: this.dataSource})
  80. }
  81. },
  82. methods: {
  83. run: function(e) {
  84. this.running = true
  85. this.results = ""
  86. this.error = false
  87. cancelAllQueries()
  88. var data = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
  89. var _this = this
  90. runQuery(data, function (data) {
  91. _this.running = false
  92. _this.showResults(data)
  93. errorLine = _this.getErrorLine()
  94. if (errorLine) {
  95. editor.getSession().addGutterDecoration(errorLine - 1, "error")
  96. editor.scrollToLine(errorLine, true, true, function () {})
  97. editor.gotoLine(errorLine, 0, true)
  98. editor.focus()
  99. }
  100. }, function (data) {
  101. _this.running = false
  102. _this.error = true
  103. _this.showResults(data)
  104. })
  105. },
  106. cancel: function(e) {
  107. this.running = false
  108. cancelAllQueries()
  109. },
  110. updateDataSource: function(dataSource) {
  111. this.dataSource = dataSource
  112. var selectize = this.selectize
  113. selectize.clearOptions()
  114. if (this.tablesXhr) {
  115. this.tablesXhr.abort()
  116. }
  117. this.tablesXhr = $.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
  118. var newOptions = []
  119. for (var i = 0; i < data.length; i++) {
  120. newOptions.push({text: data[i], value: data[i]})
  121. }
  122. selectize.clearOptions()
  123. selectize.addOption(newOptions)
  124. selectize.refreshOptions(false)
  125. })
  126. },
  127. showEditor: function() {
  128. var _this = this
  129. editor = ace.edit("editor")
  130. editor.setTheme("ace/theme/twilight")
  131. editor.getSession().setMode("ace/mode/sql")
  132. editor.setOptions({
  133. enableBasicAutocompletion: false,
  134. enableSnippets: false,
  135. enableLiveAutocompletion: false,
  136. highlightActiveLine: false,
  137. fontSize: 12,
  138. minLines: 10
  139. })
  140. editor.renderer.setShowGutter(true)
  141. editor.renderer.setPrintMarginColumn(false)
  142. editor.renderer.setPadding(10)
  143. editor.getSession().setUseWrapMode(true)
  144. editor.commands.addCommand({
  145. name: "run",
  146. bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
  147. exec: function(editor) {
  148. _this.run()
  149. },
  150. readOnly: false // false if this command should not apply in readOnly mode
  151. })
  152. // fix command+L
  153. editor.commands.removeCommands(["gotoline", "find"])
  154. this.editor = editor
  155. editor.getSession().on("change", function () {
  156. $("#query_statement").val(editor.getValue())
  157. _this.adjustHeight()
  158. })
  159. this.adjustHeight()
  160. editor.focus()
  161. },
  162. adjustHeight: function() {
  163. // http://stackoverflow.com/questions/11584061/
  164. var editor = this.editor
  165. var lines = editor.getSession().getScreenLength()
  166. if (lines < 9) {
  167. lines = 9
  168. }
  169. this.editorHeight = ((lines + 1) * 16).toString() + "px"
  170. Vue.nextTick(function () {
  171. editor.resize()
  172. })
  173. },
  174. getSQL: function() {
  175. var selectedText = editor.getSelectedText()
  176. var text = selectedText.length < 10 ? editor.getValue() : selectedText
  177. return text.replace(/\n/g, "\r\n")
  178. },
  179. getErrorLine: function() {
  180. var editor = this.editor
  181. var errorLine = this.results.substring(0, 100).includes("alert-danger") && /LINE (\d+)/g.exec(this.results)
  182. if (errorLine) {
  183. errorLine = parseInt(errorLine[1], 10)
  184. if (editor.getSelectedText().length >= 10) {
  185. errorLine += editor.getSelectionRange().start.row
  186. }
  187. return errorLine
  188. }
  189. },
  190. showResults(data) {
  191. // can't do it the Vue way due to script tags in results
  192. // this.results = data
  193. Vue.nextTick(function () {
  194. $("#results-html").html(data)
  195. })
  196. }
  197. },
  198. mounted: function() {
  199. var _this = this
  200. var $select = $("#table_names").selectize({})
  201. var selectize = $select[0].selectize
  202. selectize.on("change", function(val) {
  203. editor.setValue(previewStatement[_this.dataSource].replace("{table}", val), 1)
  204. _this.run()
  205. selectize.clear(true)
  206. selectize.blur()
  207. })
  208. this.selectize = selectize
  209. this.updateDataSource($("#query_data_source").val())
  210. var $dsSelect = $("#query_data_source").selectize({})
  211. var dsSelectize = $dsSelect[0].selectize
  212. dsSelectize.on("change", function(val) {
  213. _this.updateDataSource(val)
  214. dsSelectize.blur()
  215. })
  216. this.showEditor()
  217. }
  218. })
  219. </script>