FEATURE: 'Download Result' removes 250-row limit

This commit is contained in:
Kane York 2015-07-09 12:02:47 -07:00
parent 7b6b4f9222
commit 71c3b73ef8
6 changed files with 86 additions and 49 deletions

View File

@ -10,6 +10,11 @@ var defaultFallback = function(buffer, content, defaultRender) { defaultRender(b
function isoYMD(date) { function isoYMD(date) {
return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDate(); return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDate();
} }
function randomIdShort() {
return 'xxxxxxxx'.replace(/[xy]/g, function() {
return (Math.random() * 16 | 0).toString(16);
});
}
const QueryResultComponent = Ember.Component.extend({ const QueryResultComponent = Ember.Component.extend({
layoutName: 'explorer-query-result', layoutName: 'explorer-query-result',
@ -25,10 +30,6 @@ const QueryResultComponent = Ember.Component.extend({
return this.get('content.columns').length; return this.get('content.columns').length;
}.property('content.columns.length'), }.property('content.columns.length'),
downloadName: function() {
return this.get('query.name') + "@" + window.location.host + "-" + isoYMD(new Date()) + ".dcqresult.json";
}.property(),
duration: function() { duration: function() {
return I18n.t('explorer.run_time', {value: I18n.toNumber(this.get('content.duration'), {precision: 1})}); return I18n.t('explorer.run_time', {value: I18n.toNumber(this.get('content.duration'), {precision: 1})});
}.property('content.duration'), }.property('content.duration'),
@ -99,19 +100,45 @@ const QueryResultComponent = Ember.Component.extend({
}); });
}.property('content', 'columns.@each'), }.property('content', 'columns.@each'),
_clickDownloadButton: function() { actions: {
const self = this; downloadResult() {
const $button = this.$().find("#result-download"); const blankUrl = "about:blank";
// use $.one to do once const windowName = randomIdShort();
$button.one('mouseover', function(e) {
const a = e.target;
let resultString = "data:text/plain;base64,";
var jsonString = JSON.stringify(self.get('content'));
resultString += btoa(jsonString);
a.href = resultString; let form = document.createElement("form");
}); form.setAttribute('id', 'query-download-result');
}.on('didInsertElement'), form.setAttribute('method', 'post');
form.setAttribute('action', Discourse.getURL('/admin/plugins/explorer/queries/' + this.get('query.id') + '/run.json?download=1'));
form.setAttribute('target', windowName);
form.setAttribute('style', 'display:none;');
function addInput(form, name, value) {
let field;
field = document.createElement('input');
field.setAttribute('name', name);
field.setAttribute('value', value);
form.appendChild(field);
}
addInput(form, 'params', JSON.stringify(this.get('params')));
addInput(form, 'explain', this.get('hasExplain'));
addInput(form, 'limit', '1000000');
const newWindow = window.open(blankUrl, windowName);
Discourse.ajax('/session/csrf.json').then(function(csrf) {
addInput(form, 'authenticity_token', csrf.csrf);
document.body.appendChild(form);
form.submit();
Em.run.next('afterRender', function() {
document.body.removeChild(form);
newWindow.close();
});
});
}
},
parent: function() { return this; }.property() parent: function() { return this; }.property()

View File

@ -48,7 +48,7 @@ export default Ember.ArrayController.extend({
return this.get('selectedItem').save().then(function() { return this.get('selectedItem').save().then(function() {
const query = self.get('selectedItem'); const query = self.get('selectedItem');
query.markNotDirty(); query.markNotDirty();
self.set('editName', false); self.set('editing', false);
}).catch(function(x) { }).catch(function(x) {
popupAjaxError(x); popupAjaxError(x);
throw x; throw x;
@ -70,7 +70,7 @@ export default Ember.ArrayController.extend({
}, },
editName() { editName() {
this.set('editName', true); this.set('editing', true);
}, },
download() { download() {
@ -112,7 +112,7 @@ export default Ember.ArrayController.extend({
const query = self.get('selectedItem'); const query = self.get('selectedItem');
query.setProperties(result.getProperties(Query.updatePropertyNames)); query.setProperties(result.getProperties(Query.updatePropertyNames));
query.markNotDirty(); query.markNotDirty();
self.set('editName', false); self.set('editing', false);
}).catch(popupAjaxError).finally(function() { }).catch(popupAjaxError).finally(function() {
self.set('loading', false); self.set('loading', false);
}); });
@ -145,6 +145,9 @@ export default Ember.ArrayController.extend({
if (this.get('selectedItem.dirty')) { if (this.get('selectedItem.dirty')) {
return; return;
} }
if (this.get('runDisabled')) {
return;
}
this.set('loading', true); this.set('loading', true);
this.set('showResults', false); this.set('showResults', false);

View File

@ -28,7 +28,7 @@
<div class="query-edit {{if editName "editing"}}"> <div class="query-edit {{if editName "editing"}}">
{{#if selectedItem}} {{#if selectedItem}}
<div class="name"> <div class="name">
{{#if editName}} {{#if editing}}
{{text-field value=selectedItem.name}} {{text-field value=selectedItem.name}}
{{else}} {{else}}
<h2>{{selectedItem.name}}</h2> <h2>{{selectedItem.name}}</h2>
@ -36,7 +36,7 @@
{{/if}} {{/if}}
</div> </div>
<div class="desc"> <div class="desc">
{{#if editName}} {{#if editing}}
{{textarea value=selectedItem.description}} {{textarea value=selectedItem.description}}
{{else}} {{else}}
{{selectedItem.description}} {{selectedItem.description}}
@ -46,9 +46,7 @@
<div class="query-editor"> <div class="query-editor">
<div class="editor-panel"> <div class="editor-panel">
<div class="sql"> {{ace-editor content=selectedItem.sql mode="sql"}}
{{textarea value=selectedItem.sql class="grippie-target"}}
</div>
</div> </div>
<div class="right-panel"> <div class="right-panel">
<div class="schema grippie-target"> <div class="schema grippie-target">
@ -76,12 +74,12 @@
{{/if}} {{/if}}
</div> </div>
<div class="query-run"> <form class="query-run" {{action "run" on="submit"}}>
{{#if selectedItem.param_names}} {{#if selectedItem.param_names}}
<div class="query-params"> <div class="query-params">
<div class="param-save"> <div class="param-save">
{{d-button action="saveDefaults" label="explorer.save_params"}} {{d-button action="saveDefaults" label="explorer.save_params" type="button"}}
{{d-button action="resetParams" label="explorer.reset_params"}} {{d-button action="resetParams" label="explorer.reset_params" type="button"}}
</div> </div>
{{#each selectedItem.param_names as |pname|}} {{#each selectedItem.param_names as |pname|}}
<div class="param"> <div class="param">
@ -101,9 +99,9 @@
{{d-button action="saverun" label="explorer.saverun"}} {{d-button action="saverun" label="explorer.saverun"}}
{{/if}} {{/if}}
{{else}} {{else}}
{{d-button action="run" label="explorer.run" disabled=runDisabled class="btn-primary"}} {{d-button action="run" label="explorer.run" disabled=runDisabled class="btn-primary" type="submit"}}
{{/if}} {{/if}}
</div> </form>
<hr> <hr>
{{/if}} {{/if}}

View File

@ -1,8 +1,5 @@
<div class="result-info"> <div class="result-info">
<a class="btn" id="result-download" {{bind-attr download=downloadName}} data-auto-route="true"> {{d-button action="downloadResult" icon="download" label="explorer.download_json"}}
{{fa-icon "download"}}
{{i18n "explorer.download_json"}}
</a>
<div class="result-about"> <div class="result-about">
{{duration}} {{duration}}
</div> </div>

View File

@ -8,22 +8,22 @@
.editor-panel { .editor-panel {
float: left; float: left;
width: 65%; width: 65%;
.ace-wrapper {
.sql textarea { position: relative;
padding: 4px; height: 400px;
height: calc(400px - 8px - 2px);
width: 100%; width: 100%;
font-family: monospace; }
box-shadow: none; .ace_editor {
border: 1px solid dark-light-diff($primary, $secondary, 80%, -20%); position: absolute;
margin: 0; left: 0;
border-radius: 0; right: 0;
transition: initial; top: 0;
bottom: 0;
} }
} }
.right-panel { .right-panel {
float: right; float: right;
width: calc(35% - 10px); width: calc(35% - 0px);
.schema { .schema {
border-left: 1px solid dark-light-diff($primary, $secondary, 80%, -20%); border-left: 1px solid dark-light-diff($primary, $secondary, 80%, -20%);
height: 400px; height: 400px;

View File

@ -107,11 +107,12 @@ after_initialize do
WITH query AS ( WITH query AS (
#{query.sql} #{query.sql}
) SELECT * FROM query ) SELECT * FROM query
LIMIT #{opts[:limit] || 1000} LIMIT #{opts[:limit] || 250}
SQL SQL
time_start = Time.now time_start = Time.now
result = ActiveRecord::Base.exec_sql(sql, query_args) result = ActiveRecord::Base.exec_sql(sql, query_args)
result.check # make sure it's done
time_end = Time.now time_end = Time.now
if opts[:explain] if opts[:explain]
@ -130,8 +131,9 @@ SQL
{ {
error: err, error: err,
pg_result: result, pg_result: result,
duration_nanos: time_end.nsec - time_start.nsec, duration_secs: time_end - time_start,
explain: explain, explain: explain,
params_full: query_args.tap {|h| h.delete :xxdummy}
} }
end end
@ -347,7 +349,6 @@ SQL
end end
skip_before_filter :check_xhr, only: [:show] skip_before_filter :check_xhr, only: [:show]
def show def show
check_xhr unless params[:export] check_xhr unless params[:export]
@ -407,6 +408,7 @@ SQL
end end
end end
skip_before_filter :check_xhr, only: [:run]
# Return value: # Return value:
# success - true/false. if false, inspect the errors value. # success - true/false. if false, inspect the errors value.
# errors - array of strings. # errors - array of strings.
@ -416,11 +418,20 @@ SQL
# explain - string. (Optional - pass explain=true in the request) Postgres query plan, UNIX newlines. # 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'. # rows - array of array of strings. Results of the query. In the same order as 'columns'.
def run def run
check_xhr unless params[:download]
query = DataExplorer::Query.find(params[:id].to_i) query = DataExplorer::Query.find(params[:id].to_i)
if params[:download]
response.headers['Content-Disposition'] =
"attachment; filename=#{query.slug}@#{Slug.for(Discourse.current_hostname, 'discourse')}-#{Date.today}.dcqresult.json"
response.sending_file = true
end
query_params = MultiJson.load(params[:params]) query_params = MultiJson.load(params[:params])
opts = {current_user: current_user.username} opts = {current_user: current_user.username}
opts[:explain] = true if params[:explain] == "true" opts[:explain] = true if params[:explain] == "true"
opts[:limit] = params[:limit].to_i if params[:limit] opts[:limit] = params[:limit].to_i if params[:limit]
result = DataExplorer.run_query(query, query_params, opts) result = DataExplorer.run_query(query, query_params, opts)
if result[:error] if result[:error]
@ -431,7 +442,7 @@ SQL
err_msg = err.message err_msg = err.message
if err.is_a? ActiveRecord::StatementInvalid if err.is_a? ActiveRecord::StatementInvalid
err_class = err.original_exception.class err_class = err.original_exception.class
err_msg.gsub!("#{err_class.to_s}:", '') err_msg.gsub!("#{err_class}:", '')
else else
err_msg = "#{err_class}: #{err_msg}" err_msg = "#{err_class}: #{err_msg}"
end end
@ -446,7 +457,8 @@ SQL
json = { json = {
success: true, success: true,
errors: [], errors: [],
duration: (result[:duration_nanos].to_f / 1_000_000).round(1), duration: (result[:duration_secs].to_f * 1000).round(1),
params: result[:params_full],
columns: cols, columns: cols,
} }
json[:explain] = result[:explain] if opts[:explain] json[:explain] = result[:explain] if opts[:explain]