FEATURE: Add JSON result type component (#260)
If a column is payload or contains _payload it will be assumed it has JSON data in it, then we will show the truncated JSON in the result column with a button to show the full-screen formatted JSON using our full-screen code viewer. We also do the same if the column is the `json` postgres data type.
This commit is contained in:
parent
3e5f679fee
commit
5776aa7fc9
|
@ -17,6 +17,7 @@ import UrlViewComponent from "./result-types/url";
|
||||||
import UserViewComponent from "./result-types/user";
|
import UserViewComponent from "./result-types/user";
|
||||||
import GroupViewComponent from "./result-types/group";
|
import GroupViewComponent from "./result-types/group";
|
||||||
import HtmlViewComponent from "./result-types/html";
|
import HtmlViewComponent from "./result-types/html";
|
||||||
|
import JsonViewComponent from "./result-types/json";
|
||||||
import CategoryViewComponent from "./result-types/category";
|
import CategoryViewComponent from "./result-types/category";
|
||||||
|
|
||||||
const VIEW_COMPONENTS = {
|
const VIEW_COMPONENTS = {
|
||||||
|
@ -29,6 +30,7 @@ const VIEW_COMPONENTS = {
|
||||||
user: UserViewComponent,
|
user: UserViewComponent,
|
||||||
group: GroupViewComponent,
|
group: GroupViewComponent,
|
||||||
html: HtmlViewComponent,
|
html: HtmlViewComponent,
|
||||||
|
json: JsonViewComponent,
|
||||||
category: CategoryViewComponent,
|
category: CategoryViewComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="result-json">
|
||||||
|
<div class="result-json-value">{{@ctx.value}}</div>
|
||||||
|
<DButton
|
||||||
|
class="result-json-button"
|
||||||
|
@action={{action "viewJson"}}
|
||||||
|
@icon="ellipsis-h"
|
||||||
|
@title="explorer.view_json"
|
||||||
|
/>
|
||||||
|
</div>
|
|
@ -0,0 +1,31 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import FullscreenCodeModal from "discourse/components/modal/fullscreen-code";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { cached } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class Json extends Component {
|
||||||
|
@service dialog;
|
||||||
|
@service modal;
|
||||||
|
|
||||||
|
@cached
|
||||||
|
get parsedJson() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(this.args.ctx.value);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
viewJson() {
|
||||||
|
this.modal.show(FullscreenCodeModal, {
|
||||||
|
model: {
|
||||||
|
code: this.parsedJson
|
||||||
|
? JSON.stringify(this.parsedJson, null, 2)
|
||||||
|
: this.args.ctx.value,
|
||||||
|
codeClasses: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -416,6 +416,18 @@ table.group-reports {
|
||||||
display: block;
|
display: block;
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
}
|
}
|
||||||
|
.result-json {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-json-value {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
max-width: 250px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.explorer-pad-bottom {
|
.explorer-pad-bottom {
|
||||||
margin-bottom: 200px;
|
margin-bottom: 200px;
|
||||||
|
|
|
@ -56,6 +56,7 @@ en:
|
||||||
no: "No"
|
no: "No"
|
||||||
null_: "Null"
|
null_: "Null"
|
||||||
export: "Export"
|
export: "Export"
|
||||||
|
view_json: "View JSON"
|
||||||
save: "Save Changes"
|
save: "Save Changes"
|
||||||
saverun: "Save Changes and Run"
|
saverun: "Save Changes and Run"
|
||||||
run: "Run"
|
run: "Run"
|
||||||
|
|
|
@ -5,6 +5,10 @@ module ::DiscourseDataExplorer
|
||||||
end
|
end
|
||||||
|
|
||||||
module DataExplorer
|
module DataExplorer
|
||||||
|
# Used for ftype calls, see https://www.rubydoc.info/gems/pg/0.17.1/PG%2FResult:ftype
|
||||||
|
# and /usr/include/postgresql/server/catalog/pg_type_d.h
|
||||||
|
PG_TYPE_OID_JSON = 114
|
||||||
|
|
||||||
# Run a data explorer query on the currently connected database.
|
# Run a data explorer query on the currently connected database.
|
||||||
#
|
#
|
||||||
# @param [Query] query the Query object to run
|
# @param [Query] query the Query object to run
|
||||||
|
@ -131,6 +135,9 @@ module ::DiscourseDataExplorer
|
||||||
html: {
|
html: {
|
||||||
ignore: true,
|
ignore: true,
|
||||||
},
|
},
|
||||||
|
json: {
|
||||||
|
ignore: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -145,7 +152,6 @@ module ::DiscourseDataExplorer
|
||||||
needed_classes = {}
|
needed_classes = {}
|
||||||
ret = {}
|
ret = {}
|
||||||
col_map = {}
|
col_map = {}
|
||||||
|
|
||||||
pg_result.fields.each_with_index do |col, idx|
|
pg_result.fields.each_with_index do |col, idx|
|
||||||
rgx = column_regexes.find { |r| r.match col }
|
rgx = column_regexes.find { |r| r.match col }
|
||||||
if rgx
|
if rgx
|
||||||
|
@ -158,6 +164,8 @@ module ::DiscourseDataExplorer
|
||||||
needed_classes[cls] << idx
|
needed_classes[cls] << idx
|
||||||
elsif col =~ /^\w+_url$/
|
elsif col =~ /^\w+_url$/
|
||||||
col_map[idx] = "url"
|
col_map[idx] = "url"
|
||||||
|
elsif col =~ /^\w+_payload$/ || col == "payload" || pg_result.ftype(idx) == PG_TYPE_OID_JSON
|
||||||
|
col_map[idx] = "json"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -57,5 +57,36 @@ describe DiscourseDataExplorer::DataExplorer do
|
||||||
expect(result[:pg_result].to_a.size).to eq(1)
|
expect(result[:pg_result].to_a.size).to eq(1)
|
||||||
expect(result[:pg_result][0]["id"]).to eq(topic2.id)
|
expect(result[:pg_result][0]["id"]).to eq(topic2.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".add_extra_data" do
|
||||||
|
it "treats any column with payload in the name as 'json'" do
|
||||||
|
Fabricate(:reviewable_queued_post)
|
||||||
|
sql = <<~SQL
|
||||||
|
SELECT id, payload FROM reviewables LIMIT 10
|
||||||
|
SQL
|
||||||
|
query = DiscourseDataExplorer::Query.create!(name: "some query", sql: sql)
|
||||||
|
result = described_class.run_query(query)
|
||||||
|
_, colrender = DiscourseDataExplorer::DataExplorer.add_extra_data(result[:pg_result])
|
||||||
|
expect(colrender).to eq({ 1 => "json" })
|
||||||
|
end
|
||||||
|
|
||||||
|
it "treats columns with the actual json data type as 'json'" do
|
||||||
|
ApiKeyScope.create(
|
||||||
|
resource: "topics",
|
||||||
|
action: "update",
|
||||||
|
api_key_id: Fabricate(:api_key).id,
|
||||||
|
allowed_parameters: {
|
||||||
|
"category_id" => ["#{topic.category_id}"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
sql = <<~SQL
|
||||||
|
SELECT id, allowed_parameters FROM api_key_scopes LIMIT 10
|
||||||
|
SQL
|
||||||
|
query = DiscourseDataExplorer::Query.create!(name: "some query", sql: sql)
|
||||||
|
result = described_class.run_query(query)
|
||||||
|
_, colrender = DiscourseDataExplorer::DataExplorer.add_extra_data(result[:pg_result])
|
||||||
|
expect(colrender).to eq({ 1 => "json" })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,4 +42,18 @@ RSpec.describe "Reports", type: :system, js: true do
|
||||||
find(".query-run .btn-primary").click
|
find(".query-run .btn-primary").click
|
||||||
expect(page).to have_css(".query-results .result-header")
|
expect(page).to have_css(".query-results .result-header")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows user to run a report with a JSON column and open a fullscreen code viewer" do
|
||||||
|
Fabricate(:reviewable_queued_post)
|
||||||
|
sql = <<~SQL
|
||||||
|
SELECT id, payload FROM reviewables LIMIT 10
|
||||||
|
SQL
|
||||||
|
json_query = DiscourseDataExplorer::Query.create!(name: "some query", sql: sql)
|
||||||
|
sign_in(user)
|
||||||
|
visit("/g/group/reports/#{json_query.id}")
|
||||||
|
find(".query-run .btn-primary").click
|
||||||
|
expect(page).to have_css(".query-results .result-json")
|
||||||
|
first(".query-results .result-json .btn.result-json-button").click
|
||||||
|
expect(page).to have_css(".fullscreen-code-modal")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue