Merge branch 'refactor' into 'master'
Conflicts: renamed assets/javascripts/discourse/adapters/query.js.es6
This commit is contained in:
+3
-1
@@ -1,3 +1,5 @@
|
||||
import buildPluginAdapter from 'admin/adapters/build-plugin';
|
||||
|
||||
export default buildPluginAdapter('explorer').extend({});
|
||||
export default buildPluginAdapter('explorer').extend({
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,67 @@
|
||||
import binarySearch from 'discourse/plugins/discourse-data-explorer/discourse/lib/binary-search';
|
||||
import avatarTemplate from 'discourse/lib/avatar-template';
|
||||
|
||||
const defaultRender = function(buffer, content) {
|
||||
buffer.push(Handlebars.Utils.escapeExpression(content));
|
||||
};
|
||||
function icon_or_image_replacement(str, ctx) {
|
||||
str = Ember.get(ctx.contexts[0], str);
|
||||
if (Ember.isEmpty(str)) { return ""; }
|
||||
|
||||
if (str.indexOf('fa-') === 0) {
|
||||
return new Handlebars.SafeString("<i class='fa " + str + "'></i>");
|
||||
} else {
|
||||
return new Handlebars.SafeString("<img src='" + str + "'>");
|
||||
}
|
||||
}
|
||||
|
||||
function shorthandTinyAvatar(username, uploadId, ctx) {
|
||||
username = Ember.get(ctx.contexts[0], username);
|
||||
uploadId = Ember.get(ctx.contexts[0], uploadId);
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: "tiny",
|
||||
extraClasses: '',
|
||||
title: username,
|
||||
avatarTemplate: avatarTemplate(username, uploadId)
|
||||
}));
|
||||
}
|
||||
|
||||
const esc = Handlebars.Utils.escapeExpression;
|
||||
|
||||
const QueryRowContentComponent = Ember.Component.extend({
|
||||
tagName: "tr",
|
||||
|
||||
render(buffer) {
|
||||
transformedUserTable: function() {
|
||||
return transformedRelTable(this.get('extra.relations.user'));
|
||||
}.property('extra.relations.user'),
|
||||
|
||||
render: function(buffer) {
|
||||
const self = this;
|
||||
const row = this.get('row');
|
||||
const response = this.get('extra');
|
||||
this.get('colRenders').forEach(function(colRender, idx) {
|
||||
buffer.push("<td data-column-name=" + Handlebars.Utils.escapeExpression(colRender.name) + ">");
|
||||
colRender.render(buffer, row[idx], defaultRender, response);
|
||||
buffer.push("</td>");
|
||||
const relations = this.get('extra.relations');
|
||||
const parent = self.get('parent');
|
||||
|
||||
const parts = this.get('columnTemplates').map(function(t, idx) {
|
||||
const ctx = {};
|
||||
const params = {}
|
||||
if (t.name === "text") {
|
||||
return esc(row[idx]);
|
||||
} else if (t.name === "user") {
|
||||
ctx.user = parent.lookupUser(parseInt(row[idx]));
|
||||
if (!ctx.user) {
|
||||
return esc(row[idx]);
|
||||
}
|
||||
} else if (t.name === "badge") {
|
||||
ctx.badge = parent.lookupBadge(parseInt(row[idx]));
|
||||
params.helpers = {"icon-or-image": icon_or_image_replacement};
|
||||
} else if (t.name === "post") {
|
||||
ctx.post = parent.lookupPost(parseInt(row[idx]));
|
||||
params.helpers = {avatar: shorthandTinyAvatar};
|
||||
} else {
|
||||
ctx.value = row[idx];
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString(t.template(ctx, params));
|
||||
});
|
||||
|
||||
buffer.push("<td>" + parts.join("</td><td>") + "</td>");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export default {
|
||||
name: 'polyfill-string-endswith',
|
||||
initialize(container) {
|
||||
if (!String.prototype.endsWith) {
|
||||
String.prototype.endsWith = function(searchString, position) {
|
||||
var subjectString = this.toString();
|
||||
if (position === undefined || position > subjectString.length) {
|
||||
position = subjectString.length;
|
||||
}
|
||||
position -= searchString.length;
|
||||
var lastIndex = subjectString.indexOf(searchString, position);
|
||||
return lastIndex !== -1 && lastIndex === position;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// The binarySearch() function is licensed under the UNLICENSE
|
||||
// https://github.com/Olical/binary-search
|
||||
|
||||
// Modified for use in Discourse
|
||||
|
||||
export default function binarySearch(list, target, keyProp) {
|
||||
var min = 0;
|
||||
var max = list.length - 1;
|
||||
var guess;
|
||||
var keyProperty = keyProp || "id";
|
||||
|
||||
while (min <= max) {
|
||||
guess = Math.floor((min + max) / 2);
|
||||
|
||||
if (Em.get(list[guess], keyProperty) === target) {
|
||||
return guess;
|
||||
}
|
||||
else {
|
||||
if (Em.get(list[guess], keyProperty) < target) {
|
||||
min = guess + 1;
|
||||
}
|
||||
else {
|
||||
max = guess - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -16,14 +16,14 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="headers">
|
||||
{{#each handler in columnHandlers}}
|
||||
<th>{{handler.displayName}}</th>
|
||||
{{#each columnDispNames as |col|}}
|
||||
<th>{{col}}</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each row in rows}}
|
||||
{{query-row-content row=row colRenders=columnHandlers extra=content}}
|
||||
{{/each}}
|
||||
{{~#each row in rows}}
|
||||
{{~query-row-content row=row columnTemplates=columnTemplates parent=controller}}
|
||||
{{~/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{{! source: badge-button component }}
|
||||
<span class="user-badge {{badge.badgeTypeClassName}}" title="{{badge.displayDescription}}" data-badge-name="{{badge.name}}">{{icon-or-image badge.icon}} <span class="badge-display-name">{{badge.displayName}}</span></span>
|
||||
@@ -0,0 +1 @@
|
||||
{{{value}}}
|
||||
@@ -0,0 +1,4 @@
|
||||
<aside class="quote" data-post="{{post.post_number}}" data-topic="{{post.topic_id}}"><div class="title" style="cursor: pointer;">
|
||||
<div class="quote-controls">{{!<i class="fa fa-chevron-down" title="expand/collapse"></i>}}<a href="/t/via-quote/{{post.topic_id}}/{{post.post_number}}" title="go to the quoted post" class="quote-other-topic"></a></div>
|
||||
{{avatar post.username post.uploaded_avatar_id}}{{post.username}}:</div>
|
||||
<blockquote><p>{{{post.excerpt}}}</p></blockquote></aside>
|
||||
@@ -0,0 +1,2 @@
|
||||
{{value}}
|
||||
{{! note - this template isn't actually used, it gets short-circuited in query-row-content.js.es6}}
|
||||
@@ -0,0 +1 @@
|
||||
<a href="/users/{{user.username}}/activity">{{avatar user imageSize="tiny"}} {{user.username}}</a>
|
||||
@@ -28,7 +28,6 @@ module ::DataExplorer
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
after_initialize do
|
||||
|
||||
module ::DataExplorer
|
||||
@@ -40,6 +39,20 @@ after_initialize do
|
||||
class ValidationError < StandardError;
|
||||
end
|
||||
|
||||
class SmallBadgeSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :badge_type, :description, :icon
|
||||
end
|
||||
|
||||
class SmallPostWithExcerptSerializer < ApplicationSerializer
|
||||
attributes :id, :topic_id, :post_number, :excerpt
|
||||
attributes :username, :uploaded_avatar_id
|
||||
def excerpt
|
||||
Post.excerpt(object.cooked, 70)
|
||||
end
|
||||
def username; object.user.username; end
|
||||
def uploaded_avatar_id; object.user.uploaded_avatar_id; end
|
||||
end
|
||||
|
||||
# Run a data explorer query on the currently connected database.
|
||||
#
|
||||
# @param [DataExplorer::Query] query the Query object to run
|
||||
@@ -120,6 +133,75 @@ SQL
|
||||
}
|
||||
end
|
||||
|
||||
def self.extra_data_pluck_fields
|
||||
@extra_data_pluck_fields ||= {
|
||||
user: {class: User, fields: [:id, :username, :uploaded_avatar_id], serializer: BasicUserSerializer},
|
||||
badge: {class: Badge, fields: [:id, :name, :badge_type_id, :description, :icon], include: [:badge_type], serializer: SmallBadgeSerializer},
|
||||
post: {class: Post, fields: [:id, :topic_id, :post_number, :cooked, :user_id], include: [:user], serializer: SmallPostWithExcerptSerializer},
|
||||
topic: {class: Topic, fields: [:id, :title, :slug, :posts_count], serializer: BasicTopicSerializer},
|
||||
category: {class: Category, ignore: true},
|
||||
reltime: {ignore: true},
|
||||
html: {ignore: true},
|
||||
}
|
||||
end
|
||||
|
||||
def self.column_regexes
|
||||
@column_regexes ||=
|
||||
extra_data_pluck_fields.map do |key, val|
|
||||
if val[:class]
|
||||
/(#{val[:class].to_s.downcase})_id$/
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
def self.add_extra_data(pg_result)
|
||||
needed_classes = {}
|
||||
|
||||
pg_result.fields.each_with_index do |col, idx|
|
||||
rgx = column_regexes.find { |rgx| rgx.match col }
|
||||
if rgx
|
||||
cls = (rgx.match col)[1].to_sym
|
||||
needed_classes[cls] ||= []
|
||||
needed_classes[cls] << idx
|
||||
elsif col =~ /^(\w+)\$/
|
||||
cls = $1.to_sym
|
||||
needed_classes[cls] ||= []
|
||||
needed_classes[cls] << idx
|
||||
end
|
||||
end
|
||||
|
||||
ret = {}
|
||||
col_map = {}
|
||||
needed_classes.each do |cls, column_nums|
|
||||
next unless column_nums.present?
|
||||
support_info = extra_data_pluck_fields[cls]
|
||||
next unless support_info
|
||||
|
||||
column_nums.each do |col_n|
|
||||
col_map[col_n] = cls
|
||||
end
|
||||
|
||||
if support_info[:ignore]
|
||||
ret[cls] = []
|
||||
next
|
||||
end
|
||||
|
||||
ids = Set.new
|
||||
column_nums.each do |col_n|
|
||||
ids.merge(pg_result.column_values(col_n))
|
||||
end
|
||||
ids.delete nil
|
||||
ids.map! &:to_i
|
||||
|
||||
object_class = support_info[:class]
|
||||
all_objs = object_class.select(support_info[:fields]).
|
||||
where(id: ids.to_a.sort).includes(support_info[:include]).order(:id)
|
||||
|
||||
ret[cls] = ActiveModel::ArraySerializer.new(all_objs, each_serializer: support_info[:serializer])
|
||||
end
|
||||
[ret, col_map]
|
||||
end
|
||||
|
||||
def self.sensitive_column_names
|
||||
%w(
|
||||
#_IP_Addresses
|
||||
@@ -966,11 +1048,9 @@ SQL
|
||||
columns: cols,
|
||||
}
|
||||
json[:explain] = result[:explain] if opts[:explain]
|
||||
# TODO - special serialization
|
||||
# This is dead code in the client right now
|
||||
# if cols.any? { |col_name| special_serialization? col_name }
|
||||
# json[:relations] = DataExplorer.add_extra_data(pg_result)
|
||||
# end
|
||||
ext = DataExplorer.add_extra_data(pg_result)
|
||||
json[:colrender] = ext[1]
|
||||
json[:relations] = ext[0]
|
||||
|
||||
json[:rows] = pg_result.values
|
||||
|
||||
|
||||
Reference in New Issue
Block a user