FEATURE: get rid of the import a query modal (#127)
This is the new version of #126. Now the pick-files-button moved to core. To import a query into Data Explorer, you need to push the button and then deal with the import modal. Instead, we want just to be triggering a system file picker directly. This PR makes it happen.
This commit is contained in:
parent
23287ece95
commit
4a98cc8af8
|
@ -1,113 +0,0 @@
|
|||
import {
|
||||
default as computed,
|
||||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
fileInput: null,
|
||||
loading: false,
|
||||
expectedRootObjectName: null,
|
||||
hover: 0,
|
||||
|
||||
classNames: ["json-uploader"],
|
||||
|
||||
@on("didInsertElement")
|
||||
_initialize() {
|
||||
const $this = $(this.element);
|
||||
const fileInput = this.element.querySelector("#js-file-input");
|
||||
this.set("fileInput", fileInput);
|
||||
|
||||
$(fileInput).on("change", () => this.fileSelected(fileInput.files));
|
||||
|
||||
$this.on("dragover", (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
$this.on("dragenter", (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.set("hover", this.hover + 1);
|
||||
return false;
|
||||
});
|
||||
$this.on("dragleave", (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.set("hover", this.hover - 1);
|
||||
return false;
|
||||
});
|
||||
$this.on("drop", (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.set("hover", 0);
|
||||
this.fileSelected(e.dataTransfer.files);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
@computed("extension")
|
||||
accept(extension) {
|
||||
return (
|
||||
".json,application/json,application/x-javascript,text/json" +
|
||||
(extension ? `,${extension}` : "")
|
||||
);
|
||||
},
|
||||
|
||||
@observes("destination", "expectedRootObjectName")
|
||||
setReady() {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(this.value);
|
||||
} catch (e) {
|
||||
this.set("ready", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rootObject = parsed[this.expectedRootObjectName];
|
||||
|
||||
if (rootObject !== null && rootObject !== undefined) {
|
||||
this.set("ready", true);
|
||||
} else {
|
||||
this.set("ready", false);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
selectFile() {
|
||||
$(this.fileInput).click();
|
||||
},
|
||||
},
|
||||
|
||||
fileSelected(fileList) {
|
||||
let files = [];
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
files[i] = fileList[i];
|
||||
}
|
||||
const fileNameRegex = /\.(json|txt)$/;
|
||||
files = files.filter((file) => {
|
||||
if (fileNameRegex.test(file.name)) {
|
||||
return true;
|
||||
}
|
||||
if (file.type === "text/plain") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const firstFile = files[0];
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
this.setProperties({ value: evt.target.result, loading: false });
|
||||
};
|
||||
|
||||
reader.readAsText(firstFile);
|
||||
},
|
||||
});
|
|
@ -6,6 +6,8 @@ import {
|
|||
default as computed,
|
||||
observes,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
const NoQuery = Query.create({ name: "No queries", fake: true, group_ids: [] });
|
||||
|
||||
|
@ -30,6 +32,11 @@ export default Ember.Controller.extend({
|
|||
sortBy: ["last_run_at:desc"],
|
||||
sortedQueries: Ember.computed.sort("model", "sortBy"),
|
||||
|
||||
@computed
|
||||
acceptedImportFileTypes() {
|
||||
return ["application/json"];
|
||||
},
|
||||
|
||||
@computed("search", "sortBy")
|
||||
filteredContent(search) {
|
||||
const regexp = new RegExp(search, "i");
|
||||
|
@ -117,6 +124,36 @@ export default Ember.Controller.extend({
|
|||
.finally(() => this.set("loading", false));
|
||||
},
|
||||
|
||||
async _importQuery(file) {
|
||||
const json = await this._readFileAsTextAsync(file);
|
||||
const query = this._parseQuery(json);
|
||||
const record = this.store.createRecord("query", query);
|
||||
const response = await record.save();
|
||||
return response.target;
|
||||
},
|
||||
|
||||
_parseQuery(json) {
|
||||
const parsed = JSON.parse(json);
|
||||
const query = parsed.query;
|
||||
if (!query || !query.sql) {
|
||||
throw new TypeError();
|
||||
}
|
||||
query.id = 0; // 0 means no Id yet
|
||||
return query;
|
||||
},
|
||||
|
||||
_readFileAsTextAsync(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
dummy() {},
|
||||
|
||||
|
@ -124,9 +161,27 @@ export default Ember.Controller.extend({
|
|||
this.set("hideSchema", false);
|
||||
},
|
||||
|
||||
importQuery() {
|
||||
showModal("import-query");
|
||||
this.set("showCreate", false);
|
||||
import(files) {
|
||||
this.set("loading", true);
|
||||
const file = files[0];
|
||||
this._importQuery(file)
|
||||
.then((record) => this.addCreatedRecord(record))
|
||||
.catch((e) => {
|
||||
if (e.jqXHR) {
|
||||
popupAjaxError(e);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
bootbox.alert(I18n.t("explorer.import.unparseable_json"));
|
||||
} else if (e instanceof TypeError) {
|
||||
bootbox.alert(I18n.t("explorer.import.wrong_json"));
|
||||
} else {
|
||||
bootbox.alert(I18n.t("errors.desc.unknown"));
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("loading", false);
|
||||
});
|
||||
},
|
||||
|
||||
showCreate() {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { default as computed } from "discourse-common/utils/decorators";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
notReady: Ember.computed.not("ready"),
|
||||
|
||||
adminPluginsExplorer: Ember.inject.controller(),
|
||||
|
||||
@computed("queryFile")
|
||||
ready(queryFile) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(queryFile);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!parsed["query"];
|
||||
},
|
||||
|
||||
actions: {
|
||||
doImport() {
|
||||
const object = JSON.parse(this.queryFile).query;
|
||||
|
||||
// Slight fixup before creating object
|
||||
object.id = 0; // 0 means no Id yet
|
||||
|
||||
this.set("loading", true);
|
||||
this.store
|
||||
.createRecord("query", object)
|
||||
.save()
|
||||
.then((query) => {
|
||||
this.send("closeModal");
|
||||
this.set("loading", false);
|
||||
|
||||
const parentController = this.adminPluginsExplorer;
|
||||
parentController.addCreatedRecord(query.target);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -6,7 +6,12 @@
|
|||
<div class="query-list">
|
||||
{{text-field value=search placeholderKey="explorer.search_placeholder"}}
|
||||
{{d-button action=(action "showCreate") icon="plus" class="no-text btn-right"}}
|
||||
{{d-button action=(action "importQuery") label="explorer.import.label" icon="upload"}}
|
||||
{{pick-files-button
|
||||
class="import-btn"
|
||||
label="explorer.import.label"
|
||||
icon="upload"
|
||||
acceptedFileTypes=acceptedImportFileTypes
|
||||
onFilesPicked=(action "import")}}
|
||||
</div>
|
||||
|
||||
{{#if showCreate}}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<div class="jsfu-shade-container">
|
||||
<div class="jsfu-file">
|
||||
<input id="js-file-input" type="file" style="display:none;" accept={{accept}}>
|
||||
{{d-button class="fileSelect" action=(action "selectFile") icon="upload" label="upload_selector.select_file"}}
|
||||
{{conditional-loading-spinner condition=loading size="small"}}
|
||||
</div>
|
||||
<div class="jsfu-separator">
|
||||
<small>
|
||||
{{i18n "explorer.or"}}
|
||||
</small>
|
||||
</div>
|
||||
<div class="jsfu-paste">
|
||||
{{textarea value=value}}
|
||||
</div>
|
||||
<div class="jsfu-shade {{if hover "" "hidden"}}"><span class="text">{{d-icon "upload"}}</span></div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
{{#d-modal-body title="explorer.import.modal"}}
|
||||
<form {{action "dummy" on="submit"}}>
|
||||
<div class="modal-body">
|
||||
{{json-file-uploader value=queryFile extension=".dcquery.json"}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary" action="doImport" type="submit" disabled=notReady icon="plus" label="explorer.import.label"}}
|
||||
</div>
|
||||
</form>
|
||||
{{/d-modal-body}}
|
|
@ -315,6 +315,9 @@ table.group-reports {
|
|||
li.none {
|
||||
display: none;
|
||||
}
|
||||
.import-btn {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.recent-queries {
|
||||
|
|
|
@ -15,6 +15,8 @@ en:
|
|||
import:
|
||||
label: "Import"
|
||||
modal: "Import A Query"
|
||||
unparseable_json: "Unparseable JSON file."
|
||||
wrong_json: "Wrong JSON file. A JSON file should contain a 'query' object, which should at least have an 'sql' property."
|
||||
help:
|
||||
label: "Help"
|
||||
modal_title: "Data Explorer Help"
|
||||
|
|
Loading…
Reference in New Issue