Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEV: Split the Query Listing and Query Editing code #356

Merged
merged 10 commits into from
Feb 10, 2025
8 changes: 4 additions & 4 deletions app/controllers/discourse_data_explorer/query_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def show
end

return raise Discourse::NotFound if !guardian.user_can_access_query?(@query) || @query.hidden
render_serialized @query, QuerySerializer, root: "query"
render_serialized @query, QueryDetailsSerializer, root: "query"
end

def groups
Expand Down Expand Up @@ -68,7 +68,7 @@ def group_reports_show
query_group = QueryGroup.find_by(query_id: @query.id, group_id: @group.id)

render json: {
query: serialize_data(@query, QuerySerializer, root: nil),
query: serialize_data(@query, QueryDetailsSerializer, root: nil),
query_group: serialize_data(query_group, QueryGroupSerializer, root: nil),
}
end
Expand All @@ -93,7 +93,7 @@ def create
)
group_ids = params.require(:query)[:group_ids]
group_ids&.each { |group_id| query.query_groups.find_or_create_by!(group_id: group_id) }
render_serialized query, QuerySerializer, root: "query"
render_serialized query, QueryDetailsSerializer, root: "query"
end

def update
Expand All @@ -107,7 +107,7 @@ def update
group_ids&.each { |group_id| @query.query_groups.find_or_create_by!(group_id: group_id) }
end

render_serialized @query, QuerySerializer, root: "query"
render_serialized @query, QueryDetailsSerializer, root: "query"
rescue ValidationError => e
render_json_error e.message
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module ::DiscourseDataExplorer
class QueryDetailsSerializer < QuerySerializer
attributes :id,
:sql,
:name,
:description,
:param_info,
:created_at,
:username,
:group_ids,
:last_run_at,
:hidden,
:user_id

def param_info
object&.params&.uniq { |p| p.identifier }&.map(&:to_hash)
end
end
end
16 changes: 1 addition & 15 deletions app/serializers/discourse_data_explorer/query_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,7 @@

module ::DiscourseDataExplorer
class QuerySerializer < ActiveModel::Serializer
attributes :id,
:sql,
:name,
:description,
:param_info,
:created_at,
:username,
:group_ids,
:last_run_at,
:hidden,
:user_id

def param_info
object&.params&.uniq { |p| p.identifier }&.map(&:to_hash)
end
attributes :id, :name, :description, :username, :group_ids, :last_run_at, :user_id

def username
object&.user&.username
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { tracked } from "@glimmer/tracking";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { Promise } from "rsvp";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bind } from "discourse/lib/decorators";
import { i18n } from "discourse-i18n";

export default class PluginsExplorerController extends Controller {
@service dialog;
@service appEvents;
@service router;

@tracked sortByProperty = "last_run_at";
@tracked sortDescending = true;
@tracked params;
@tracked search;
@tracked newQueryName;
@tracked showCreate;
@tracked loading = false;

explain = false;
acceptedImportFileTypes = ["application/json"];
order = null;
form = null;

get sortedQueries() {
const sortedQueries = this.model.sortBy(this.sortByProperty);
return this.sortDescending ? sortedQueries.reverse() : sortedQueries;
}

get parsedParams() {
return this.params ? JSON.parse(this.params) : null;
}

get filteredContent() {
const regexp = new RegExp(this.search, "i");
return this.sortedQueries.filter(
(result) => regexp.test(result.name) || regexp.test(result.description)
);
}

get createDisabled() {
return (this.newQueryName || "").trim().length === 0;
}

addCreatedRecord(record) {
this.model.pushObject(record);
this.router.transitionTo(
"adminPlugins.explorer.queries.details",
record.id
);
}

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

@bind
dragMove(e) {
if (!e.movementY && !e.movementX) {
return;
}

const editPane = document.querySelector(".query-editor");
const target = editPane.querySelector(".panels-flex");
const grippie = editPane.querySelector(".grippie");

// we need to get the initial height / width of edit pane
// before we manipulate the size
if (!this.initialPaneWidth && !this.originalPaneHeight) {
this.originalPaneWidth = target.clientWidth;
this.originalPaneHeight = target.clientHeight;
}

const newHeight = Math.max(
this.originalPaneHeight,
target.clientHeight + e.movementY
);
const newWidth = Math.max(
this.originalPaneWidth,
target.clientWidth + e.movementX
);

target.style.height = newHeight + "px";
target.style.width = newWidth + "px";
grippie.style.width = newWidth + "px";
this.appEvents.trigger("ace:resize");
}

@bind
scrollTop() {
window.scrollTo(0, 0);
}

@action
async import(files) {
try {
this.loading = true;
const file = files[0];
const record = await this._importQuery(file);
this.addCreatedRecord(record);
} catch (e) {
if (e.jqXHR) {
popupAjaxError(e);
} else if (e instanceof SyntaxError) {
this.dialog.alert(i18n("explorer.import.unparseable_json"));
} else if (e instanceof TypeError) {
this.dialog.alert(i18n("explorer.import.wrong_json"));
} else {
this.dialog.alert(i18n("errors.desc.unknown"));
// eslint-disable-next-line no-console
console.error(e);
}
} finally {
this.loading = false;
}
}

@action
displayCreate() {
this.showCreate = true;
}

@action
resetParams() {
this.selectedItem.resetParams();
}

@action
updateSortProperty(property) {
if (this.sortByProperty === property) {
this.sortDescending = !this.sortDescending;
} else {
this.sortByProperty = property;
this.sortDescending = true;
}
}

@action
async create() {
try {
const name = this.newQueryName.trim();
this.loading = true;
this.showCreate = false;
const result = await this.store.createRecord("query", { name }).save();
this.addCreatedRecord(result.target);
} catch (error) {
popupAjaxError(error);
} finally {
this.loading = false;
}
}

@action
updateSearch(value) {
this.search = value;
}

@action
updateNewQueryName(value) {
this.newQueryName = value;
}
}
Loading