diff --git a/assets/javascripts/discourse/components/query-result.js b/assets/javascripts/discourse/components/query-result.js
index e126396..9d25e16 100644
--- a/assets/javascripts/discourse/components/query-result.js
+++ b/assets/javascripts/discourse/components/query-result.js
@@ -17,6 +17,7 @@ import UrlViewComponent from "./result-types/url";
import UserViewComponent from "./result-types/user";
import GroupViewComponent from "./result-types/group";
import HtmlViewComponent from "./result-types/html";
+import JsonViewComponent from "./result-types/json";
import CategoryViewComponent from "./result-types/category";
const VIEW_COMPONENTS = {
@@ -29,6 +30,7 @@ const VIEW_COMPONENTS = {
user: UserViewComponent,
group: GroupViewComponent,
html: HtmlViewComponent,
+ json: JsonViewComponent,
category: CategoryViewComponent,
};
diff --git a/assets/javascripts/discourse/components/result-types/json.hbs b/assets/javascripts/discourse/components/result-types/json.hbs
new file mode 100644
index 0000000..3fd4fe1
--- /dev/null
+++ b/assets/javascripts/discourse/components/result-types/json.hbs
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/assets/javascripts/discourse/components/result-types/json.js b/assets/javascripts/discourse/components/result-types/json.js
new file mode 100644
index 0000000..59dcfc4
--- /dev/null
+++ b/assets/javascripts/discourse/components/result-types/json.js
@@ -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: "",
+ },
+ });
+ }
+}
diff --git a/assets/stylesheets/explorer.scss b/assets/stylesheets/explorer.scss
index 9637fc2..9170786 100644
--- a/assets/stylesheets/explorer.scss
+++ b/assets/stylesheets/explorer.scss
@@ -416,6 +416,18 @@ table.group-reports {
display: block;
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 {
margin-bottom: 200px;
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 51f83e6..0d93697 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -56,6 +56,7 @@ en:
no: "No"
null_: "Null"
export: "Export"
+ view_json: "View JSON"
save: "Save Changes"
saverun: "Save Changes and Run"
run: "Run"
diff --git a/lib/discourse_data_explorer/data_explorer.rb b/lib/discourse_data_explorer/data_explorer.rb
index d5f4c3e..1bd002d 100644
--- a/lib/discourse_data_explorer/data_explorer.rb
+++ b/lib/discourse_data_explorer/data_explorer.rb
@@ -5,6 +5,10 @@ module ::DiscourseDataExplorer
end
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.
#
# @param [Query] query the Query object to run
@@ -131,6 +135,9 @@ module ::DiscourseDataExplorer
html: {
ignore: true,
},
+ json: {
+ ignore: true,
+ },
}
end
@@ -145,7 +152,6 @@ module ::DiscourseDataExplorer
needed_classes = {}
ret = {}
col_map = {}
-
pg_result.fields.each_with_index do |col, idx|
rgx = column_regexes.find { |r| r.match col }
if rgx
@@ -158,6 +164,8 @@ module ::DiscourseDataExplorer
needed_classes[cls] << idx
elsif col =~ /^\w+_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
diff --git a/spec/data_explorer_spec.rb b/spec/data_explorer_spec.rb
index ba93454..8cb7a6a 100644
--- a/spec/data_explorer_spec.rb
+++ b/spec/data_explorer_spec.rb
@@ -57,5 +57,36 @@ describe DiscourseDataExplorer::DataExplorer do
expect(result[:pg_result].to_a.size).to eq(1)
expect(result[:pg_result][0]["id"]).to eq(topic2.id)
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
diff --git a/spec/system/reports_spec.rb b/spec/system/reports_spec.rb
index dcedbaf..a0da064 100644
--- a/spec/system/reports_spec.rb
+++ b/spec/system/reports_spec.rb
@@ -42,4 +42,18 @@ RSpec.describe "Reports", type: :system, js: true do
find(".query-run .btn-primary").click
expect(page).to have_css(".query-results .result-header")
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