FEATURE: 'Download Result' removes 250-row limit
This commit is contained in:
parent
7b6b4f9222
commit
71c3b73ef8
|
@ -10,6 +10,11 @@ var defaultFallback = function(buffer, content, defaultRender) { defaultRender(b
|
|||
function isoYMD(date) {
|
||||
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({
|
||||
layoutName: 'explorer-query-result',
|
||||
|
@ -25,10 +30,6 @@ const QueryResultComponent = Ember.Component.extend({
|
|||
return this.get('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() {
|
||||
return I18n.t('explorer.run_time', {value: I18n.toNumber(this.get('content.duration'), {precision: 1})});
|
||||
}.property('content.duration'),
|
||||
|
@ -99,19 +100,45 @@ const QueryResultComponent = Ember.Component.extend({
|
|||
});
|
||||
}.property('content', 'columns.@each'),
|
||||
|
||||
_clickDownloadButton: function() {
|
||||
const self = this;
|
||||
const $button = this.$().find("#result-download");
|
||||
// use $.one to do once
|
||||
$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);
|
||||
actions: {
|
||||
downloadResult() {
|
||||
const blankUrl = "about:blank";
|
||||
const windowName = randomIdShort();
|
||||
|
||||
a.href = resultString;
|
||||
let form = document.createElement("form");
|
||||
form.setAttribute('id', 'query-download-result');
|
||||
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();
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
parent: function() { return this; }.property()
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ export default Ember.ArrayController.extend({
|
|||
return this.get('selectedItem').save().then(function() {
|
||||
const query = self.get('selectedItem');
|
||||
query.markNotDirty();
|
||||
self.set('editName', false);
|
||||
self.set('editing', false);
|
||||
}).catch(function(x) {
|
||||
popupAjaxError(x);
|
||||
throw x;
|
||||
|
@ -70,7 +70,7 @@ export default Ember.ArrayController.extend({
|
|||
},
|
||||
|
||||
editName() {
|
||||
this.set('editName', true);
|
||||
this.set('editing', true);
|
||||
},
|
||||
|
||||
download() {
|
||||
|
@ -112,7 +112,7 @@ export default Ember.ArrayController.extend({
|
|||
const query = self.get('selectedItem');
|
||||
query.setProperties(result.getProperties(Query.updatePropertyNames));
|
||||
query.markNotDirty();
|
||||
self.set('editName', false);
|
||||
self.set('editing', false);
|
||||
}).catch(popupAjaxError).finally(function() {
|
||||
self.set('loading', false);
|
||||
});
|
||||
|
@ -145,6 +145,9 @@ export default Ember.ArrayController.extend({
|
|||
if (this.get('selectedItem.dirty')) {
|
||||
return;
|
||||
}
|
||||
if (this.get('runDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('loading', true);
|
||||
this.set('showResults', false);
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="query-edit {{if editName "editing"}}">
|
||||
{{#if selectedItem}}
|
||||
<div class="name">
|
||||
{{#if editName}}
|
||||
{{#if editing}}
|
||||
{{text-field value=selectedItem.name}}
|
||||
{{else}}
|
||||
<h2>{{selectedItem.name}}</h2>
|
||||
|
@ -36,7 +36,7 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{#if editName}}
|
||||
{{#if editing}}
|
||||
{{textarea value=selectedItem.description}}
|
||||
{{else}}
|
||||
{{selectedItem.description}}
|
||||
|
@ -46,9 +46,7 @@
|
|||
|
||||
<div class="query-editor">
|
||||
<div class="editor-panel">
|
||||
<div class="sql">
|
||||
{{textarea value=selectedItem.sql class="grippie-target"}}
|
||||
</div>
|
||||
{{ace-editor content=selectedItem.sql mode="sql"}}
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="schema grippie-target">
|
||||
|
@ -76,12 +74,12 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="query-run">
|
||||
<form class="query-run" {{action "run" on="submit"}}>
|
||||
{{#if selectedItem.param_names}}
|
||||
<div class="query-params">
|
||||
<div class="param-save">
|
||||
{{d-button action="saveDefaults" label="explorer.save_params"}}
|
||||
{{d-button action="resetParams" label="explorer.reset_params"}}
|
||||
{{d-button action="saveDefaults" label="explorer.save_params" type="button"}}
|
||||
{{d-button action="resetParams" label="explorer.reset_params" type="button"}}
|
||||
</div>
|
||||
{{#each selectedItem.param_names as |pname|}}
|
||||
<div class="param">
|
||||
|
@ -101,9 +99,9 @@
|
|||
{{d-button action="saverun" label="explorer.saverun"}}
|
||||
{{/if}}
|
||||
{{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}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<div class="result-info">
|
||||
<a class="btn" id="result-download" {{bind-attr download=downloadName}} data-auto-route="true">
|
||||
{{fa-icon "download"}}
|
||||
{{i18n "explorer.download_json"}}
|
||||
</a>
|
||||
{{d-button action="downloadResult" icon="download" label="explorer.download_json"}}
|
||||
<div class="result-about">
|
||||
{{duration}}
|
||||
</div>
|
||||
|
|
|
@ -8,22 +8,22 @@
|
|||
.editor-panel {
|
||||
float: left;
|
||||
width: 65%;
|
||||
|
||||
.sql textarea {
|
||||
padding: 4px;
|
||||
height: calc(400px - 8px - 2px);
|
||||
.ace-wrapper {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
box-shadow: none;
|
||||
border: 1px solid dark-light-diff($primary, $secondary, 80%, -20%);
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
transition: initial;
|
||||
}
|
||||
.ace_editor {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.right-panel {
|
||||
float: right;
|
||||
width: calc(35% - 10px);
|
||||
width: calc(35% - 0px);
|
||||
.schema {
|
||||
border-left: 1px solid dark-light-diff($primary, $secondary, 80%, -20%);
|
||||
height: 400px;
|
||||
|
|
22
plugin.rb
22
plugin.rb
|
@ -107,11 +107,12 @@ after_initialize do
|
|||
WITH query AS (
|
||||
#{query.sql}
|
||||
) SELECT * FROM query
|
||||
LIMIT #{opts[:limit] || 1000}
|
||||
LIMIT #{opts[:limit] || 250}
|
||||
SQL
|
||||
|
||||
time_start = Time.now
|
||||
result = ActiveRecord::Base.exec_sql(sql, query_args)
|
||||
result.check # make sure it's done
|
||||
time_end = Time.now
|
||||
|
||||
if opts[:explain]
|
||||
|
@ -130,8 +131,9 @@ SQL
|
|||
{
|
||||
error: err,
|
||||
pg_result: result,
|
||||
duration_nanos: time_end.nsec - time_start.nsec,
|
||||
duration_secs: time_end - time_start,
|
||||
explain: explain,
|
||||
params_full: query_args.tap {|h| h.delete :xxdummy}
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -347,7 +349,6 @@ SQL
|
|||
end
|
||||
|
||||
skip_before_filter :check_xhr, only: [:show]
|
||||
|
||||
def show
|
||||
check_xhr unless params[:export]
|
||||
|
||||
|
@ -407,6 +408,7 @@ SQL
|
|||
end
|
||||
end
|
||||
|
||||
skip_before_filter :check_xhr, only: [:run]
|
||||
# Return value:
|
||||
# success - true/false. if false, inspect the errors value.
|
||||
# errors - array of strings.
|
||||
|
@ -416,11 +418,20 @@ SQL
|
|||
# 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
|
||||
check_xhr unless params[:download]
|
||||
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])
|
||||
|
||||
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]
|
||||
|
@ -431,7 +442,7 @@ SQL
|
|||
err_msg = err.message
|
||||
if err.is_a? ActiveRecord::StatementInvalid
|
||||
err_class = err.original_exception.class
|
||||
err_msg.gsub!("#{err_class.to_s}:", '')
|
||||
err_msg.gsub!("#{err_class}:", '')
|
||||
else
|
||||
err_msg = "#{err_class}: #{err_msg}"
|
||||
end
|
||||
|
@ -446,7 +457,8 @@ SQL
|
|||
json = {
|
||||
success: true,
|
||||
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,
|
||||
}
|
||||
json[:explain] = result[:explain] if opts[:explain]
|
||||
|
|
Loading…
Reference in New Issue