diff --git a/app/assets/javascripts/discourse/controllers/user-summary.js.es6 b/app/assets/javascripts/discourse/controllers/user-summary.js.es6
new file mode 100644
index 000000000..b8c414521
--- /dev/null
+++ b/app/assets/javascripts/discourse/controllers/user-summary.js.es6
@@ -0,0 +1,13 @@
+export default Ember.Controller.extend({
+ needs: ['user'],
+ user: Em.computed.alias('controllers.user.model'),
+ moreTopics: function(){
+ return this.get('model.topics').length > 5;
+ }.property('model'),
+ moreReplies: function(){
+ return this.get('model.replies').length > 5;
+ }.property('model'),
+ moreBadges: function(){
+ return this.get('model.badges').length > 5;
+ }.property('model')
+});
diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6
index 2dbeea576..eff6ef3d7 100644
--- a/app/assets/javascripts/discourse/models/user.js.es6
+++ b/app/assets/javascripts/discourse/models/user.js.es6
@@ -10,6 +10,7 @@ import UserBadge from 'discourse/models/user-badge';
import UserActionStat from 'discourse/models/user-action-stat';
import UserAction from 'discourse/models/user-action';
import Group from 'discourse/models/group';
+import Topic from 'discourse/models/topic';
const User = RestModel.extend({
@@ -355,6 +356,38 @@ const User = RestModel.extend({
});
}
});
+ },
+
+ summary() {
+ return Discourse.ajax(`/users/${this.get("username_lower")}/summary.json`)
+ .then(json => {
+ const topicMap = {};
+
+ json.topics.forEach(t => {
+ topicMap[t.id] = Topic.create(t);
+ });
+
+ const badgeMap = {};
+ Badge.createFromJson(json).forEach(b => {
+ badgeMap[b.id] = b;
+ });
+ const summary = json["user_summary"];
+
+ summary.replies.forEach(r => {
+ r.topic = topicMap[r.topic_id];
+ r.url = r.topic.urlForPostNumber(r.post_number);
+ r.createdAt = new Date(r.created_at);
+ });
+
+ summary.topics = summary.topic_ids.map(id => topicMap[id]);
+
+ summary.badges = summary.badges.map(ub => {
+ const badge = badgeMap[ub.badge_id];
+ badge.count = ub.count;
+ return badge;
+ });
+ return summary;
+ });
}
});
diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
index ae3b2f05f..f8c278db1 100644
--- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6
+++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
@@ -58,6 +58,7 @@ export default function() {
// User routes
this.resource('users');
this.resource('user', { path: '/users/:username' }, function() {
+ this.route('summary');
this.resource('userActivity', { path: '/activity' }, function() {
this.route('topics');
this.route('replies');
diff --git a/app/assets/javascripts/discourse/routes/user-statistics.js.es6 b/app/assets/javascripts/discourse/routes/user-statistics.js.es6
deleted file mode 100644
index 610a10489..000000000
--- a/app/assets/javascripts/discourse/routes/user-statistics.js.es6
+++ /dev/null
@@ -1,2 +0,0 @@
-export default Discourse.Route.extend({
-});
diff --git a/app/assets/javascripts/discourse/routes/user-summary.js.es6 b/app/assets/javascripts/discourse/routes/user-summary.js.es6
new file mode 100644
index 000000000..4b24ecc6a
--- /dev/null
+++ b/app/assets/javascripts/discourse/routes/user-summary.js.es6
@@ -0,0 +1,5 @@
+export default Discourse.Route.extend({
+ model() {
+ return this.modelFor("User").summary();
+ }
+});
diff --git a/app/assets/javascripts/discourse/templates/user/summary.hbs b/app/assets/javascripts/discourse/templates/user/summary.hbs
new file mode 100644
index 000000000..d6736b63a
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/user/summary.hbs
@@ -0,0 +1,62 @@
+{{#if model.replies.length}}
+
+
{{i18n "user.summary.top_replies"}}
+{{#each reply in model.replies}}
+
+ -
+ {{reply.topic.title}} {{#if reply.like_count}}{{reply.like_count}}{{/if}} {{format-date reply.createdAt format="tiny" noTitle="true"}}
+
+
+{{/each}}
+{{#if moreReplies}}
+{{#link-to "userActivity.replies" user class="more"}}{{i18n "user.summary.more_replies"}}{{/link-to}}
+{{/if}}
+
+{{/if}}
+
+{{#if model.topics.length}}
+
+
{{i18n "user.summary.top_topics"}}
+{{#each topic in model.topics}}
+
+ -
+ {{topic.title}} {{#if topic.like_count}}{{topic.like_count}}{{/if}} {{format-date topic.createdAt format="tiny" noTitle="true"}}
+
+
+{{/each}}
+{{#if moreTopics}}
+{{#link-to "userActivity.topics" user class="more"}}{{i18n "user.summary.more_topics"}}{{/link-to}}
+{{/if}}
+
+{{/if}}
+
+
+
{{i18n "user.summary.stats"}}
+
+ - {{i18n "user.summary.topic_count"}}
+ - {{model.topic_count}}
+ - {{i18n "user.summary.post_count"}}
+ - {{model.post_count}}
+ - {{i18n "user.summary.likes_given"}}
+ - {{model.likes_given}}
+ - {{i18n "user.summary.likes_received"}}
+ - {{model.likes_received}}
+ - {{i18n "user.summary.days_visited"}}
+ - {{model.days_visited}}
+ - {{i18n "user.summary.posts_read_count"}}
+ - {{model.posts_read_count}}
+
+
+
+{{#if model.badges.length}}
+
+
{{i18n "user.summary.top_badges"}}
+{{#each badge in model.badges}}
+ {{user-badge badge=badge count=badge.count}}
+{{/each}}
+{{#if moreBadges}}
+{{#link-to "user.badges" user class="more"}}{{i18n "user.summary.more_badges"}}{{/link-to}}
+{{/if}}
+
+{{/if}}
+
diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs
index c8f5eec79..6d9ac7af3 100644
--- a/app/assets/javascripts/discourse/templates/user/user.hbs
+++ b/app/assets/javascripts/discourse/templates/user/user.hbs
@@ -149,7 +149,7 @@
- - {{#link-to 'userActivity'}}{{i18n 'user.activity_stream'}}{{/link-to}}
+ - {{#link-to 'userActivity'}}{{i18n 'user.activity_stream'}}{{/link-to}}
{{#if showNotificationsTab}}
-
{{#link-to 'userNotifications'}}
@@ -167,6 +167,7 @@
{{#if showBadges}}
- {{#link-to 'user.badges'}}{{fa-icon "certificate"}}{{i18n 'badges.title'}}{{/link-to}}
{{/if}}
+ - {{#link-to 'user.summary'}}{{i18n 'user.summary.title'}}{{/link-to}}
{{#if model.can_edit}}
- {{#link-to 'preferences'}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}
{{/if}}
diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss
index fac2284a6..8ec1047f4 100644
--- a/app/assets/stylesheets/common/base/user.scss
+++ b/app/assets/stylesheets/common/base/user.scss
@@ -149,3 +149,61 @@
}
}
+.top-section {
+ display: inline-block;
+ width: 45%;
+ max-width: 500px;
+ padding-right: 20px;
+ vertical-align: top;
+ margin-bottom: 30px;
+ .more {
+ display: block;
+ margin-top: 10px;
+ color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
+ }
+ h3 {
+ margin-bottom: 15px;
+ }
+ .relative-date {
+ color: lighten($primary, 40%);
+ font-size: 0.8em;
+ margin-left: 5px;
+ }
+ .like-count {
+ margin-left: 5px;
+ }
+ ul {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+ li {
+ margin: 0;
+ padding: 8px 0;
+ .fa-heart {
+ margin-left: 3px;
+ }
+ }
+ }
+
+ dt,dd {
+ float:left;
+ }
+ dd {
+ min-width: 80px;
+ text-align: right;
+ }
+ dt {
+ clear: left;
+ min-width: 100px;
+ color: dark-light-choose(scale-color($primary, $lightness: 25%), scale-color($secondary, $lightness: 75%));
+ }
+}
+
+@media all
+and (max-width : 600px) {
+ .top-section {
+ width: 90%;
+ }
+}
+
+
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 806ab3650..e92b0ede8 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -176,6 +176,13 @@ class UsersController < ApplicationController
end
end
+ def summary
+ user = fetch_user_from_params
+ summary = UserSummary.new(user, guardian)
+ serializer = UserSummarySerializer.new(summary, scope: guardian)
+ render_json_dump(serializer)
+ end
+
def invited
inviter = fetch_user_from_params
offset = params[:offset].to_i || 0
diff --git a/app/models/user.rb b/app/models/user.rb
index a06697519..3d56abf4f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -619,7 +619,7 @@ class User < ActiveRecord::Base
user_badges.select('distinct badge_id').count
end
- def featured_user_badges
+ def featured_user_badges(limit=3)
user_badges
.joins(:badge)
.order("CASE WHEN badges.id = (SELECT MAX(ub2.badge_id) FROM user_badges ub2
@@ -629,7 +629,7 @@ class User < ActiveRecord::Base
.includes(:user, :granted_by, badge: :badge_type)
.where("user_badges.id in (select min(u2.id)
from user_badges u2 where u2.user_id = ? group by u2.badge_id)", id)
- .limit(3)
+ .limit(limit)
end
def self.count_by_signup_date(start_date, end_date)
diff --git a/app/models/user_summary.rb b/app/models/user_summary.rb
new file mode 100644
index 000000000..c2e295898
--- /dev/null
+++ b/app/models/user_summary.rb
@@ -0,0 +1,56 @@
+# ViewModel used on Summary tab on User page
+
+class UserSummary
+
+ MAX_FEATURED_BADGES = 7
+ MAX_TOPICS = 6
+
+ alias :read_attribute_for_serialization :send
+
+ def initialize(user, guardian)
+ @user = user
+ @guardian = guardian
+ end
+
+ def topics
+ Topic
+ .secured(@guardian)
+ .listable_topics
+ .where(user: @user)
+ .order('like_count desc, created_at asc')
+ .includes(:user, :category)
+ .limit(MAX_TOPICS)
+ end
+
+ def replies
+ Post
+ .secured(@guardian)
+ .where(user: @user)
+ .where('post_number > 1')
+ .where('topics.archetype <> ?', Archetype.private_message)
+ .order('posts.like_count desc, posts.created_at asc')
+ .includes(:user, {topic: :category})
+ .references(:topic)
+ .limit(MAX_TOPICS)
+ end
+
+ def badges
+ user_badges = @user.user_badges
+ user_badges = user_badges.group(:badge_id)
+ .select(UserBadge.attribute_names.map {|x|
+ "MAX(#{x}) as #{x}" }, 'COUNT(*) as count')
+ .includes(badge: [:badge_grouping, :badge_type])
+ .includes(post: :topic)
+ .includes(:granted_by)
+ .limit(MAX_FEATURED_BADGES)
+ end
+
+ def user_stat
+ @user.user_stat
+ end
+
+ delegate :likes_given, :likes_received, :days_visited,
+ :posts_read_count, :topic_count, :post_count,
+ to: :user_stat
+
+end
diff --git a/app/serializers/user_summary_serializer.rb b/app/serializers/user_summary_serializer.rb
new file mode 100644
index 000000000..4be285f4a
--- /dev/null
+++ b/app/serializers/user_summary_serializer.rb
@@ -0,0 +1,18 @@
+class UserSummarySerializer < ApplicationSerializer
+ class TopicSerializer < BasicTopicSerializer
+ attributes :like_count, :slug, :created_at
+ end
+
+ class ReplySerializer < ApplicationSerializer
+ attributes :post_number, :like_count, :created_at
+ has_one :topic, serializer: TopicSerializer
+ end
+
+ has_many :topics, serializer: TopicSerializer
+ has_many :replies, serializer: ReplySerializer, embed: :object
+ has_many :badges, serializer: UserBadgeSerializer, embed: :object
+
+ attributes :likes_given, :likes_received, :posts_read_count,
+ :days_visited, :topic_count, :post_count
+
+end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 77b13ca62..b5091b084 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -703,6 +703,23 @@ en:
ok: "Your password looks good."
instructions: "At least %{count} characters."
+ summary:
+ title: "Summary"
+ stats: "Stats"
+ topic_count: "Topics Created"
+ post_count: "Posts Created"
+ likes_given: "Likes Given"
+ likes_received: "Likes Received"
+ days_visited: "Days Visited"
+ posts_read_count: "Posts Read"
+ top_replies: "Top Replies"
+ top_topics: "Top Topics"
+ top_badges: "Top Badges"
+ more_topics: "More Topics"
+ more_replies: "More Replies"
+ more_badges: "More Badges"
+
+
associated_accounts: "Logins"
ip_address:
title: "Last IP Address"
diff --git a/config/routes.rb b/config/routes.rb
index db779bd73..daf6ba56b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -302,10 +302,13 @@ Discourse::Application.routes.draw do
put "users/:username/preferences/card-badge" => "users#update_card_badge", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/staff-info" => "users#staff_info", constraints: {username: USERNAME_ROUTE_FORMAT}
+ get "users/:username/summary" => "users#summary", constraints: {username: USERNAME_ROUTE_FORMAT}
+
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/invited_count" => "users#invited_count", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/invited/:filter" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
post "users/action/send_activation_email" => "users#send_activation_email"
+ get "users/:username/summary" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 9547d957f..969e0756d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -1607,4 +1607,19 @@ describe UsersController do
end
+ context '#summary' do
+
+ it "generates summary info" do
+ user = Fabricate(:user)
+ create_post(user: user)
+
+ xhr :get, :summary, username: user.username_lower
+ expect(response).to be_success
+ json = JSON.parse(response.body)
+
+ expect(json["user_summary"]["topic_count"]).to eq(1)
+ expect(json["user_summary"]["post_count"]).to eq(1)
+ end
+ end
+
end
diff --git a/spec/models/user_stat_spec.rb b/spec/models/user_stat_spec.rb
index dcd5f8242..e977d3b08 100644
--- a/spec/models/user_stat_spec.rb
+++ b/spec/models/user_stat_spec.rb
@@ -102,6 +102,4 @@ describe UserStat do
end
end
-
-
end