Bunch of progress. Tuesday 1PM
This commit is contained in:
parent
78dafcc631
commit
dba181d92e
|
@ -1,5 +1,6 @@
|
||||||
import showModal from 'discourse/lib/show-modal';
|
import showModal from 'discourse/lib/show-modal';
|
||||||
import Query from 'discourse/plugins/discourse-data-explorer/discourse/models/query';
|
import Query from 'discourse/plugins/discourse-data-explorer/discourse/models/query';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export default Ember.ArrayController.extend({
|
export default Ember.ArrayController.extend({
|
||||||
selectedQueryId: null,
|
selectedQueryId: null,
|
||||||
|
@ -20,24 +21,16 @@ export default Ember.ArrayController.extend({
|
||||||
});
|
});
|
||||||
}.property('selectedQueryId'),
|
}.property('selectedQueryId'),
|
||||||
|
|
||||||
|
addCreatedRecord(record) {
|
||||||
|
this.pushObject(record);
|
||||||
|
this.set('selectedQueryId', Ember.get(record, 'id'));
|
||||||
|
this.get('selectedItem').set('dirty', false);
|
||||||
|
this.set('results', null);
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
dummy() {},
|
dummy() {},
|
||||||
|
|
||||||
create() {
|
|
||||||
const self = this;
|
|
||||||
this.set('loading', true);
|
|
||||||
this.set('showCreate', false);
|
|
||||||
var newQuery = this.store.createRecord('query', {name: this.get('newQueryName')});
|
|
||||||
newQuery.save().then(function(result) {
|
|
||||||
self.pushObject(result.target);
|
|
||||||
self.set('selectedQueryId', result.target.id);
|
|
||||||
self.set('selectedItem.dirty', false);
|
|
||||||
self.set('results', null);
|
|
||||||
}).finally(function() {
|
|
||||||
self.set('loading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
importQuery() {
|
importQuery() {
|
||||||
showModal('import-query');
|
showModal('import-query');
|
||||||
this.set('showCreate', false);
|
this.set('showCreate', false);
|
||||||
|
@ -51,6 +44,22 @@ export default Ember.ArrayController.extend({
|
||||||
this.set('editName', true);
|
this.set('editName', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
download() {
|
||||||
|
window.open(this.get('selectedItem.downloadUrl'), "_blank");
|
||||||
|
},
|
||||||
|
|
||||||
|
create() {
|
||||||
|
const self = this;
|
||||||
|
this.set('loading', true);
|
||||||
|
this.set('showCreate', false);
|
||||||
|
var newQuery = this.store.createRecord('query', {name: this.get('newQueryName')});
|
||||||
|
newQuery.save().then(function(result) {
|
||||||
|
self.addCreatedRecord(result.target);
|
||||||
|
}).catch(popupAjaxError).finally(function() {
|
||||||
|
self.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
const self = this;
|
const self = this;
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
|
@ -58,7 +67,7 @@ export default Ember.ArrayController.extend({
|
||||||
const query = self.get('selectedItem');
|
const query = self.get('selectedItem');
|
||||||
query.markNotDirty();
|
query.markNotDirty();
|
||||||
self.set('editName', false);
|
self.set('editName', false);
|
||||||
}).finally(function() {
|
}).catch(popupAjaxError).finally(function() {
|
||||||
self.set('loading', false);
|
self.set('loading', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -72,7 +81,29 @@ export default Ember.ArrayController.extend({
|
||||||
query.markNotDirty();
|
query.markNotDirty();
|
||||||
self.set('editName', false);
|
self.set('editName', false);
|
||||||
self.set('results', null);
|
self.set('results', null);
|
||||||
}).finally(function() {
|
}).catch(popupAjaxError).finally(function() {
|
||||||
|
self.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const self = this;
|
||||||
|
const query = this.get('selectedItem');
|
||||||
|
this.set('loading', true);
|
||||||
|
this.store.destroyRecord('query', query).then(function() {
|
||||||
|
query.set('destroyed', true);
|
||||||
|
}).catch(popupAjaxError).finally(function() {
|
||||||
|
self.set('loading', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
recover() {
|
||||||
|
const self = this;
|
||||||
|
const query = this.get('selectedItem');
|
||||||
|
this.set('loading', true);
|
||||||
|
query.save().then(function() {
|
||||||
|
query.set('destroyed', false);
|
||||||
|
}).catch(popupAjaxError).finally(function() {
|
||||||
self.set('loading', false);
|
self.set('loading', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -93,7 +124,7 @@ export default Ember.ArrayController.extend({
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
self.set('results', result);
|
self.set('results', result);
|
||||||
}).finally(function() {
|
}).catch(popupAjaxError).finally(function() {
|
||||||
self.set('loading', false);
|
self.set('loading', false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export default Ember.Controller.extend(ModalFunctionality, {
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
notReady: Em.computed.not('ready'),
|
notReady: Em.computed.not('ready'),
|
||||||
|
@ -30,16 +31,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
self.set('loading', false);
|
self.set('loading', false);
|
||||||
|
|
||||||
const parentController = self.get('controllers.admin-plugins-explorer');
|
const parentController = self.get('controllers.admin-plugins-explorer');
|
||||||
parentController.pushObject(query);
|
parentController.addCreatedRecord(query.target);
|
||||||
parentController.set('selectedItem', query);
|
}).catch(popupAjaxError);
|
||||||
}).catch(function(xhr) {
|
|
||||||
self.set('loading', false);
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
bootbox.alert(xhr.responseJSON.errors.join("<br>"));
|
|
||||||
} else {
|
|
||||||
bootbox.alert(I18n.t('generic_error'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,19 +12,36 @@ Query = RestModel.extend({
|
||||||
this.set('dirty', false);
|
this.set('dirty', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
downloadUrl: function() {
|
||||||
|
// TODO - can we change this to use the store/adapter?
|
||||||
|
return Discourse.getURL("/admin/plugins/explorer/queries/" + this.get('id') + ".json?export=1");
|
||||||
|
}.property('id'),
|
||||||
|
|
||||||
listName: function() {
|
listName: function() {
|
||||||
|
let name = this.get('name');
|
||||||
if (this.get('dirty')) {
|
if (this.get('dirty')) {
|
||||||
return this.get('name') + " (*)";
|
name += " (*)";
|
||||||
}
|
}
|
||||||
return this.get('name');
|
if (this.get('destroyed')) {
|
||||||
}.property('name', 'dirty'),
|
name += " (deleted)";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}.property('name', 'dirty', 'destroyed'),
|
||||||
|
|
||||||
createProperties() {
|
createProperties() {
|
||||||
|
if (this.get('sql')) {
|
||||||
|
// Importing
|
||||||
|
return this.updateProperties();
|
||||||
|
}
|
||||||
return this.getProperties("name");
|
return this.getProperties("name");
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProperties() {
|
updateProperties() {
|
||||||
return this.getProperties(Query.updatePropertyNames);
|
let props = this.getProperties(Query.updatePropertyNames);
|
||||||
|
if (this.get('destroyed')) {
|
||||||
|
props.id = this.get('id');
|
||||||
|
}
|
||||||
|
return props;
|
||||||
},
|
},
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
|
|
|
@ -17,7 +17,18 @@
|
||||||
<div class="sql">
|
<div class="sql">
|
||||||
{{textarea value=selectedItem.sql}}
|
{{textarea value=selectedItem.sql}}
|
||||||
</div>
|
</div>
|
||||||
{{d-button action="save" label="explorer.save" disabled=saveDisabled}}
|
<div class="left-buttons">
|
||||||
{{d-button action="discard" class="no-text btn-danger" icon="undo" disabled=saveDisabled}}
|
{{d-button action="save" label="explorer.save" disabled=saveDisabled}}
|
||||||
{{d-button action="run" label="explorer.run" disabled=runDisabled}}
|
{{d-button action="run" label="explorer.run" disabled=runDisabled}}
|
||||||
|
{{d-button action="download" label="explorer.export" disabled=runDisabled icon="download"}}
|
||||||
|
</div>
|
||||||
|
<div class="right-buttons">
|
||||||
|
{{#if selectedItem.destroyed}}
|
||||||
|
{{d-button action="recover" class="" icon="undo" label="explorer.recover"}}
|
||||||
|
{{else}}
|
||||||
|
{{d-button action="discard" class="btn-danger" icon="undo" label="explorer.undo" disabled=saveDisabled}}
|
||||||
|
{{d-button action="destroy" class="btn-danger" icon="trash" label="explorer.delete"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<h3>Queries</h3>
|
<h3>Queries</h3>
|
||||||
<div class="query-list">
|
<div class="query-list">
|
||||||
{{combo-box valueAttribute="id" value=selectedQueryId nameProperty="name" content=content castInteger="true"}}
|
{{combo-box valueAttribute="id" value=selectedQueryId nameProperty="listName" content=content castInteger="true"}}
|
||||||
{{d-button action="showCreate" icon="plus" class="no-text"}}
|
{{d-button action="showCreate" icon="plus" class="no-text"}}
|
||||||
{{d-button action="importQuery" label="explorer.import.label" icon="upload"}}
|
{{d-button action="importQuery" label="explorer.import.label" icon="upload"}}
|
||||||
</div>
|
</div>
|
||||||
{{#if showCreate}}
|
{{#if showCreate}}
|
||||||
<div class="query-create">
|
<div class="query-create">
|
||||||
{{text-field value=newQueryName placeholderKey="explorer.create_placeholder"}}
|
{{text-field value=newQueryName placeholderKey="explorer.create_placeholder"}}
|
||||||
{{d-button action="create" label="explorer.create" icon="plus"}}
|
{{d-button action="create" label="explorer.create" icon="plus" class="btn-primary"}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -14,11 +14,18 @@
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
.sql textarea {
|
.sql textarea {
|
||||||
width: 800px;
|
width: calc(100% - 8px);
|
||||||
height: 100px;
|
height: 100px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
border-color: $tertiary;
|
border-color: $tertiary;
|
||||||
}
|
}
|
||||||
|
.left-buttons {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.right-buttons {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.clear { clear: both; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-list, .query-edit, .query-results {
|
.query-list, .query-edit, .query-results {
|
||||||
|
|
|
@ -31,3 +31,6 @@ en:
|
||||||
export: "Export"
|
export: "Export"
|
||||||
save: "Save Changes"
|
save: "Save Changes"
|
||||||
run: "Run Query"
|
run: "Run Query"
|
||||||
|
undo: "Revert"
|
||||||
|
delete: "Delete"
|
||||||
|
recover: "Undelete Query"
|
||||||
|
|
53
plugin.rb
53
plugin.rb
|
@ -125,6 +125,13 @@ SQL
|
||||||
class DataExplorer::Query
|
class DataExplorer::Query
|
||||||
attr_accessor :id, :name, :description, :sql, :defaults
|
attr_accessor :id, :name, :description, :sql, :defaults
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@name = 'Unnamed Query'
|
||||||
|
@description = 'Enter a description here'
|
||||||
|
@sql = 'SELECT 1'
|
||||||
|
@defaults = {}
|
||||||
|
end
|
||||||
|
|
||||||
def param_names
|
def param_names
|
||||||
param_info = DataExplorer.extract_params sql
|
param_info = DataExplorer.extract_params sql
|
||||||
param_info[:names]
|
param_info[:names]
|
||||||
|
@ -171,16 +178,19 @@ SQL
|
||||||
def to_hash
|
def to_hash
|
||||||
{
|
{
|
||||||
id: @id,
|
id: @id,
|
||||||
name: @name || 'Query',
|
name: @name,
|
||||||
description: @description || '',
|
description: @description,
|
||||||
sql: @sql || 'SELECT 1',
|
sql: @sql,
|
||||||
defaults: @defaults || {},
|
defaults: @defaults,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find(id)
|
def self.find(id, opts={})
|
||||||
hash = DataExplorer.pstore_get("q:#{id}")
|
hash = DataExplorer.pstore_get("q:#{id}")
|
||||||
raise Discourse::NotFound unless hash
|
unless hash
|
||||||
|
return DataExplorer::Query.new if opts[:ignore_deleted]
|
||||||
|
raise Discourse::NotFound
|
||||||
|
end
|
||||||
from_hash hash
|
from_hash hash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -212,7 +222,6 @@ SQL
|
||||||
require_dependency 'application_controller'
|
require_dependency 'application_controller'
|
||||||
class DataExplorer::QueryController < ::ApplicationController
|
class DataExplorer::QueryController < ::ApplicationController
|
||||||
requires_plugin DataExplorer.plugin_name
|
requires_plugin DataExplorer.plugin_name
|
||||||
skip_before_filter :check_xhr, only: [:show]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
# guardian.ensure_can_use_data_explorer!
|
# guardian.ensure_can_use_data_explorer!
|
||||||
|
@ -220,14 +229,15 @@ SQL
|
||||||
render_serialized queries, DataExplorer::QuerySerializer, root: 'queries'
|
render_serialized queries, DataExplorer::QuerySerializer, root: 'queries'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
skip_before_filter :check_xhr, only: [:show]
|
||||||
def show
|
def show
|
||||||
|
check_xhr unless params[:export]
|
||||||
|
|
||||||
query = DataExplorer::Query.find(params[:id].to_i)
|
query = DataExplorer::Query.find(params[:id].to_i)
|
||||||
|
|
||||||
if params[:export]
|
if params[:export]
|
||||||
response.headers['Content-Disposition'] = "attachment; filename=#{query.slug}.json"
|
response.headers['Content-Disposition'] = "attachment; filename=#{query.slug}.dcquery.json"
|
||||||
response.sending_file = true
|
response.sending_file = true
|
||||||
else
|
|
||||||
check_xhr
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# guardian.ensure_can_see! query
|
# guardian.ensure_can_see! query
|
||||||
|
@ -243,18 +253,26 @@ SQL
|
||||||
# guardian.ensure_can_create_explorer_query!
|
# guardian.ensure_can_create_explorer_query!
|
||||||
|
|
||||||
query = DataExplorer::Query.from_hash params.require(:query)
|
query = DataExplorer::Query.from_hash params.require(:query)
|
||||||
# Set the ID _only_ if undeleting
|
binding.pry
|
||||||
if params[:recover]
|
query.id = nil # json import will assign an id, which is wrong
|
||||||
query.id = params[:id].to_i
|
|
||||||
end
|
|
||||||
query.save
|
query.save
|
||||||
|
|
||||||
render_serialized query, DataExplorer::QuerySerializer, root: 'queries'
|
render_serialized query, DataExplorer::QuerySerializer, root: 'query'
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
query = DataExplorer::Query.find(params[:id].to_i)
|
query = DataExplorer::Query.find(params[:id].to_i, ignore_deleted: true)
|
||||||
hash = params.require(:query)
|
hash = params.require(:query)
|
||||||
|
|
||||||
|
# Undeleting
|
||||||
|
unless query.id
|
||||||
|
if hash[:id]
|
||||||
|
query.id = hash[:id].to_i
|
||||||
|
else
|
||||||
|
raise Discourse::NotFound
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
[:name, :sql, :defaults, :description].each do |sym|
|
[:name, :sql, :defaults, :description].each do |sym|
|
||||||
query.send("#{sym}=", hash[sym]) if hash[sym]
|
query.send("#{sym}=", hash[sym]) if hash[sym]
|
||||||
end
|
end
|
||||||
|
@ -266,7 +284,8 @@ SQL
|
||||||
def destroy
|
def destroy
|
||||||
query = DataExplorer::Query.find(params[:id].to_i)
|
query = DataExplorer::Query.find(params[:id].to_i)
|
||||||
query.destroy
|
query.destroy
|
||||||
render nothing: true
|
|
||||||
|
render json: {success: true, errors: []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|
Loading…
Reference in New Issue