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:
Andrei Prigorshnev 2021-08-20 14:26:48 +04:00 committed by GitHub
parent 23287ece95
commit 4a98cc8af8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 186 deletions

View File

@ -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);
},
});

View File

@ -6,6 +6,8 @@ import {
default as computed, default as computed,
observes, observes,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import I18n from "I18n";
import { Promise } from "rsvp";
const NoQuery = Query.create({ name: "No queries", fake: true, group_ids: [] }); 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"], sortBy: ["last_run_at:desc"],
sortedQueries: Ember.computed.sort("model", "sortBy"), sortedQueries: Ember.computed.sort("model", "sortBy"),
@computed
acceptedImportFileTypes() {
return ["application/json"];
},
@computed("search", "sortBy") @computed("search", "sortBy")
filteredContent(search) { filteredContent(search) {
const regexp = new RegExp(search, "i"); const regexp = new RegExp(search, "i");
@ -117,6 +124,36 @@ export default Ember.Controller.extend({
.finally(() => this.set("loading", false)); .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: { actions: {
dummy() {}, dummy() {},
@ -124,9 +161,27 @@ export default Ember.Controller.extend({
this.set("hideSchema", false); this.set("hideSchema", false);
}, },
importQuery() { import(files) {
showModal("import-query"); this.set("loading", true);
this.set("showCreate", false); 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() { showCreate() {

View File

@ -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);
},
},
});

View File

@ -6,7 +6,12 @@
<div class="query-list"> <div class="query-list">
{{text-field value=search placeholderKey="explorer.search_placeholder"}} {{text-field value=search placeholderKey="explorer.search_placeholder"}}
{{d-button action=(action "showCreate") icon="plus" class="no-text btn-right"}} {{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> </div>
{{#if showCreate}} {{#if showCreate}}

View File

@ -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>

View File

@ -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}}

View File

@ -315,6 +315,9 @@ table.group-reports {
li.none { li.none {
display: none; display: none;
} }
.import-btn {
display: flex;
}
} }
.recent-queries { .recent-queries {

View File

@ -15,6 +15,8 @@ en:
import: import:
label: "Import" label: "Import"
modal: "Import A Query" 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: help:
label: "Help" label: "Help"
modal_title: "Data Explorer Help" modal_title: "Data Explorer Help"