Merge branch 'refactor' into 'master'

Conflicts:
	renamed assets/javascripts/discourse/adapters/query.js.es6
This commit is contained in:
Kane York
2015-08-25 21:39:22 -07:00
12 changed files with 272 additions and 245 deletions
@@ -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>
+86 -6
View File
@@ -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