mirror of
https://github.com/discourse/discourse-data-explorer.git
synced 2025-07-08 23:32:44 +00:00
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) {
|
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()
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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}}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
22
plugin.rb
22
plugin.rb
@ -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]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user