diff --git a/assets/javascripts/discourse/components/param-field.js.es6 b/assets/javascripts/discourse/components/param-field.js.es6 index 89d1192..17dfb37 100644 --- a/assets/javascripts/discourse/components/param-field.js.es6 +++ b/assets/javascripts/discourse/components/param-field.js.es6 @@ -4,5 +4,5 @@ export default Ember.TextField.extend({ this.get('params')[this.get('pname')] = value; } return this.get('params')[this.get('pname')]; - }.property() + }.property('params', 'pname') }); diff --git a/assets/javascripts/discourse/controllers/admin-plugins-explorer.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-explorer.js.es6 index 1a74ec2..47ffd0c 100644 --- a/assets/javascripts/discourse/controllers/admin-plugins-explorer.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-plugins-explorer.js.es6 @@ -137,14 +137,17 @@ export default Ember.ArrayController.extend({ explain: this.get('explain') } }).then(function(result) { + self.set('results', result); if (!result.success) { - return popupAjaxError(result); + self.set('showResults', false); + return; } - console.log(result); self.set('showResults', true); + }).catch(function(result) { + self.set('showResults', false); self.set('results', result); - }).catch(popupAjaxError).finally(function() { + }).finally(function() { self.set('loading', false); }); } diff --git a/assets/javascripts/discourse/models/query.js.es6 b/assets/javascripts/discourse/models/query.js.es6 index a6bb643..2c965c3 100644 --- a/assets/javascripts/discourse/models/query.js.es6 +++ b/assets/javascripts/discourse/models/query.js.es6 @@ -5,10 +5,20 @@ Query = RestModel.extend({ dirty: false, params: {}, + _init: function() { + this._super(); + if (!this.get('options')) { + this.set('options', {defaults:{}}); + } + this.set('dirty', false); + }.on('init'), + _initParams: function() { this.resetParams(); }.on('init').observes('param_names'), + options: Em.computed.alias('qopts'), + markDirty: function() { this.set('dirty', true); }.observes('name', 'description', 'sql', 'options', 'options.defaults'), @@ -37,7 +47,11 @@ Query = RestModel.extend({ const currentParams = this.get('params'); let defaults = {}; (this.get('param_names') || []).forEach(function(name) { - defaults[name] = currentParams[name]; + if (currentParams[name]) { + defaults[name] = currentParams[name]; + } else { + delete defaults[name]; + } }); this.set('options.defaults', defaults); }, @@ -80,7 +94,7 @@ Query = RestModel.extend({ }); Query.reopenClass({ - updatePropertyNames: ["name", "description", "sql", "options"] + updatePropertyNames: ["name", "description", "sql", "qopts"] }); export default Query; diff --git a/assets/javascripts/discourse/templates/admin/plugins-explorer.hbs b/assets/javascripts/discourse/templates/admin/plugins-explorer.hbs index c34c7e5..3d1396f 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-explorer.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-explorer.hbs @@ -1,24 +1,38 @@ -

Queries

+

Queries

+
{{combo-box valueAttribute="id" value=selectedQueryId nameProperty="listName" content=content castInteger="true" nameChanges="true"}} {{d-button action="showCreate" icon="plus" class="no-text"}} {{d-button action="importQuery" label="explorer.import.label" icon="upload"}}
+ {{#if showCreate}}
{{text-field value=newQueryName placeholderKey="explorer.create_placeholder"}} {{d-button action="create" label="explorer.create" icon="plus" class="btn-primary"}}
{{/if}} +
+
{{partial "admin/plugins-explorer-show" model=selectedItem}}
+ +{{conditional-loading-spinner condition=loading}} +
{{#if selectedItem.param_names}}
+
+ {{d-button action="saveDefaults" label="explorer.save_params"}} + {{d-button action="resetParams" label="explorer.reset_params"}} +
{{#each selectedItem.param_names as |pname|}} - {{param-field params=selectedItem.params pname=pname}} {{pname}} +
+ {{param-field params=selectedItem.params pname=pname}} + {{pname}} +
{{/each}}
{{/if}} @@ -27,10 +41,17 @@
{{d-button action="run" label="explorer.run" disabled=runDisabled}} +
-{{conditional-loading-spinner condition=loading}} + {{#if results}}
- {{query-result query=selectedItem content=results}} + {{#if showResults}} + {{query-result query=selectedItem content=results}} + {{else}} + {{#each results.errors as |err|}} +
{{~err}}
+ {{/each}} + {{/if}}
{{/if}} diff --git a/assets/stylesheets/explorer.scss b/assets/stylesheets/explorer.scss index 96bf019..7b5c041 100644 --- a/assets/stylesheets/explorer.scss +++ b/assets/stylesheets/explorer.scss @@ -33,6 +33,18 @@ input { margin: 9px; } + .param { + display: inline-block; + overflow-x: visible; + } + .param-save { + float: right; + margin: 9px; + } + .param-name { + display: inline-block; + width: 70px; + } } .query-list, .query-edit, .query-results, .query-params { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e4a238c..1484ea8 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -38,3 +38,5 @@ en: run_time: "Query completed in {{value}} ms." column: "Column {{number}}" explain_label: "Include query plan?" + save_params: "Set Defaults" + reset_params: "Reset" diff --git a/plugin.rb b/plugin.rb index b3b83d1..876f4b7 100644 --- a/plugin.rb +++ b/plugin.rb @@ -79,6 +79,15 @@ after_initialize do query_args = (query.qopts[:defaults] || {}).with_indifferent_access.merge(params) + # Rudimentary types + query_args.map! do |arg| + if arg =~ /\A\d+\z/ + arg.to_i + else + arg + end + end + time_start, time_end, explain, err, result = nil begin ActiveRecord::Base.connection.transaction do @@ -89,7 +98,7 @@ after_initialize do /* * DataExplorer Query - * Query: /admin/plugins/explorer/#{query.id} + * Query: /admin/plugins/explorer?id=#{query.id} * Started by: #{opts[:current_user]} */ WITH query AS ( @@ -290,11 +299,20 @@ SQL render json: {success: true, errors: []} end + # Return value: + # success - true/false. if false, inspect the errors value. + # errors - array of strings. + # params - hash. Echo of the query parameters as executed. + # duration - float. Time to execute the query, in milliseconds, to 1 decimal place. + # columns - array of strings. Titles of the returned columns, in order. + # explain - string. (Optional - pass explain=true in the request) Postgres query plan, UNIX newlines. + # rows - array of array of strings. Results of the query. In the same order as 'columns'. def run query = DataExplorer::Query.find(params[:id].to_i) query_params = MultiJson.load(params[:params]) opts = {current_user: current_user.username} opts[:explain] = true if params[:explain] == "true" + opts[:limit] = params[:limit].to_i if params[:limit] result = DataExplorer.run_query(query, query_params, opts) if result[:error] @@ -320,7 +338,6 @@ SQL json = { success: true, errors: [], - params: query_params, duration: (result[:duration_nanos].to_f / 1_000_000).round(1), columns: cols, }