FEATURE: you can not drill down and see why you have badges
Clicking on badges filters down the list to a particular user.
This commit is contained in:
parent
a055c37939
commit
ca3e2b4da3
17 changed files with 188 additions and 27 deletions
app
assets
javascripts/discourse
components
controllers
models
routes
templates
stylesheets/common/base
controllers
models
serializers
config/locales
spec/controllers
|
@ -3,5 +3,13 @@ export default Ember.Component.extend({
|
||||||
|
|
||||||
showGrantCount: function() {
|
showGrantCount: function() {
|
||||||
return this.get('count') && this.get('count') > 1;
|
return this.get('count') && this.get('count') > 1;
|
||||||
}.property('count')
|
}.property('count'),
|
||||||
|
|
||||||
|
badgeUrl: function(){
|
||||||
|
// NOTE: I tried using a link-to helper here but the queryParams mean it fails
|
||||||
|
var username = this.get('user.username_lower') || '';
|
||||||
|
username = username !== '' ? "?username=" + username : '';
|
||||||
|
return this.get('badge.url') + username;
|
||||||
|
}.property("badge", "user")
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
import UserBadge from 'discourse/models/user-badge';
|
import UserBadge from 'discourse/models/user-badge';
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
|
queryParams: ['username'],
|
||||||
noMoreBadges: false,
|
noMoreBadges: false,
|
||||||
userBadges: null,
|
userBadges: null,
|
||||||
needs: ["application"],
|
needs: ["application"],
|
||||||
|
|
||||||
|
user: function(){
|
||||||
|
if (this.get("username")) {
|
||||||
|
return this.get('userBadges')[0].get('user');
|
||||||
|
}
|
||||||
|
}.property("username"),
|
||||||
|
|
||||||
|
grantCount: function() {
|
||||||
|
if (this.get("username")) {
|
||||||
|
return this.get('userBadges.grant_count');
|
||||||
|
} else {
|
||||||
|
return this.get('model.grant_count');
|
||||||
|
}
|
||||||
|
}.property('username', 'model', 'userBadges'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
loadMore() {
|
loadMore() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const userBadges = this.get('userBadges');
|
const userBadges = this.get('userBadges');
|
||||||
|
|
||||||
UserBadge.findByBadgeId(this.get('model.id'), {
|
UserBadge.findByBadgeId(this.get('model.id'), {
|
||||||
offset: userBadges.length
|
offset: userBadges.length,
|
||||||
|
username: this.get('username'),
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
userBadges.pushObjects(result);
|
userBadges.pushObjects(result);
|
||||||
if(userBadges.length === 0){
|
if(userBadges.length === 0){
|
||||||
|
@ -22,11 +38,12 @@ export default Ember.Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
layoutClass: function(){
|
layoutClass: function(){
|
||||||
|
var user = this.get("user") ? " single-user" : "";
|
||||||
var ub = this.get("userBadges");
|
var ub = this.get("userBadges");
|
||||||
if(ub && ub[0] && ub[0].post_id){
|
if(ub && ub[0] && ub[0].post_id){
|
||||||
return "user-badge-with-posts";
|
return "user-badge-with-posts" + user;
|
||||||
} else {
|
} else {
|
||||||
return "user-badge-no-posts";
|
return "user-badge-no-posts" + user;
|
||||||
}
|
}
|
||||||
}.property("userBadges"),
|
}.property("userBadges"),
|
||||||
|
|
||||||
|
@ -34,7 +51,7 @@ export default Ember.Controller.extend({
|
||||||
if (this.get('noMoreBadges')) { return false; }
|
if (this.get('noMoreBadges')) { return false; }
|
||||||
|
|
||||||
if (this.get('userBadges')) {
|
if (this.get('userBadges')) {
|
||||||
return this.get('model.grant_count') > this.get('userBadges.length');
|
return this.get('grantCount') > this.get('userBadges.length');
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
export default Ember.ArrayController.extend({
|
export default Ember.ArrayController.extend({
|
||||||
|
needs: ["user"],
|
||||||
|
user: Em.computed.alias("controllers.user.model"),
|
||||||
sortProperties: ['badge.badge_type.sort_order', 'badge.name'],
|
sortProperties: ['badge.badge_type.sort_order', 'badge.name'],
|
||||||
orderBy: function(ub1, ub2){
|
orderBy: function(ub1, ub2){
|
||||||
var sr1 = ub1.get('badge.badge_type.sort_order');
|
var sr1 = ub1.get('badge.badge_type.sort_order');
|
||||||
|
|
|
@ -5,6 +5,10 @@ const Badge = RestModel.extend({
|
||||||
|
|
||||||
newBadge: Em.computed.none('id'),
|
newBadge: Em.computed.none('id'),
|
||||||
|
|
||||||
|
url: function() {
|
||||||
|
return Discourse.getURL(`/badges/${this.get('id')}/${this.get('slug')}`);
|
||||||
|
}.property(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@private
|
@private
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ UserBadge.reopenClass({
|
||||||
if ("user_badge" in json) {
|
if ("user_badge" in json) {
|
||||||
userBadges = [json.user_badge];
|
userBadges = [json.user_badge];
|
||||||
} else {
|
} else {
|
||||||
userBadges = json.user_badges;
|
userBadges = (json.user_badge_info && json.user_badge_info.user_badges) || json.user_badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
userBadges = userBadges.map(function(userBadgeJson) {
|
userBadges = userBadges.map(function(userBadgeJson) {
|
||||||
|
@ -73,6 +73,10 @@ UserBadge.reopenClass({
|
||||||
if ("user_badge" in json) {
|
if ("user_badge" in json) {
|
||||||
return userBadges[0];
|
return userBadges[0];
|
||||||
} else {
|
} else {
|
||||||
|
if (json.user_badge_info) {
|
||||||
|
userBadges.grant_count = json.user_badge_info.grant_count;
|
||||||
|
userBadges.username = json.user_badge_info.username;
|
||||||
|
}
|
||||||
return userBadges;
|
return userBadges;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,11 @@ import UserBadge from 'discourse/models/user-badge';
|
||||||
import Badge from 'discourse/models/badge';
|
import Badge from 'discourse/models/badge';
|
||||||
|
|
||||||
export default Discourse.Route.extend({
|
export default Discourse.Route.extend({
|
||||||
|
queryParams: {
|
||||||
|
username: {
|
||||||
|
refreshModel: true
|
||||||
|
}
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
didTransition() {
|
didTransition() {
|
||||||
this.controllerFor("badges/show")._showFooter();
|
this.controllerFor("badges/show")._showFooter();
|
||||||
|
@ -24,10 +29,13 @@ export default Discourse.Route.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel(model) {
|
afterModel(model,transition) {
|
||||||
return UserBadge.findByBadgeId(model.get("id")).then(userBadges => {
|
const username = transition.queryParams && transition.queryParams.username;
|
||||||
|
|
||||||
|
return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
|
||||||
this.userBadges = userBadges;
|
this.userBadges = userBadges;
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
titleToken() {
|
titleToken() {
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
<div class='badge'>{{user-badge badge=model}}</div>
|
<div class='badge'>{{user-badge badge=model}}</div>
|
||||||
<div class='description'>{{{model.displayDescriptionHtml}}}</div>
|
<div class='description'>{{{model.displayDescriptionHtml}}}</div>
|
||||||
<div class='grant-count'>{{i18n 'badges.granted' count=model.grant_count}}</div>
|
{{#unless user}}
|
||||||
|
<div class='grant-count'>{{i18n 'badges.granted' count=grantCount}}</div>
|
||||||
|
{{/unless}}
|
||||||
<div class='info'>{{i18n 'badges.allow_title'}} {{{view.allowTitle}}}<br>{{i18n 'badges.multiple_grant'}} {{{view.multipleGrant}}}
|
<div class='info'>{{i18n 'badges.allow_title'}} {{{view.allowTitle}}}<br>{{i18n 'badges.multiple_grant'}} {{{view.multipleGrant}}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,23 +24,48 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if user}}
|
||||||
|
<div class='badge-user-info'>
|
||||||
|
{{#link-to 'user' user}}
|
||||||
|
{{avatar user imageSize="extra_large"}}
|
||||||
|
<div class="details clearfix">
|
||||||
|
{{poster-name post=user}}
|
||||||
|
</div>
|
||||||
|
{{/link-to}}
|
||||||
|
<div class='earned'>
|
||||||
|
{{i18n 'badges.earned_n_times' count=grantCount}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if userBadges}}
|
{{#if userBadges}}
|
||||||
<div class={{unbound layoutClass}}>
|
<div class={{unbound layoutClass}}>
|
||||||
{{#each ub in userBadges}}
|
{{#each ub in userBadges}}
|
||||||
<div class="badge-user">
|
<div class="badge-user">
|
||||||
{{#link-to 'user' ub.user classNames="badge-info"}}
|
{{#if user}}
|
||||||
{{avatar ub.user imageSize="large"}}
|
{{format-date ub.granted_at}}
|
||||||
<div class="details">
|
{{else}}
|
||||||
<span class="username">{{ub.user.username}}</span>
|
{{#link-to 'user' ub.user classNames="badge-info"}}
|
||||||
{{format-date ub.granted_at}}
|
{{avatar ub.user imageSize="large"}}
|
||||||
</div>
|
<div class="details">
|
||||||
{{/link-to}}
|
<span class="username">{{ub.user.username}}</span>
|
||||||
|
{{format-date ub.granted_at}}
|
||||||
|
</div>
|
||||||
|
{{/link-to}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if ub.post_number}}
|
{{#if ub.post_number}}
|
||||||
<a class="post-link" href="{{unbound ub.topic.url}}/{{unbound ub.post_number}}">{{{ub.topic.fancyTitle}}}</a>
|
<a class="post-link" href="{{unbound ub.topic.url}}/{{unbound ub.post_number}}">{{{ub.topic.fancyTitle}}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
{{#unless canLoadMore}}
|
||||||
|
{{#if user}}
|
||||||
|
<a class='load-more' href='{{model.url}}'>{{i18n 'badges.more_with_badge'}}</a>
|
||||||
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{conditional-loading-spinner condition=canLoadMore}}
|
{{conditional-loading-spinner condition=canLoadMore}}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{#link-to 'badges.show' badge}}
|
<a href="{{badgeUrl}}">
|
||||||
{{#badge-button badge=badge}}
|
{{#badge-button badge=badge}}
|
||||||
{{#if showGrantCount}}
|
{{#if showGrantCount}}
|
||||||
<span class="count">(× {{count}})</span>
|
<span class="count">(× {{count}})</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/badge-button}}
|
{{/badge-button}}
|
||||||
{{/link-to}}
|
</a>
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
{{#if showBadges}}
|
{{#if showBadges}}
|
||||||
<div class="badge-section">
|
<div class="badge-section">
|
||||||
{{#each ub in user.featured_user_badges}}
|
{{#each ub in user.featured_user_badges}}
|
||||||
{{user-badge badge=ub.badge}}
|
{{user-badge badge=ub.badge user=user}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if showMoreBadges}}
|
{{#if showMoreBadges}}
|
||||||
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<section class='user-content user-badges-list'>
|
<section class='user-content user-badges-list'>
|
||||||
{{#each ub in controller}}
|
{{#each ub in controller}}
|
||||||
{{user-badge badge=ub.badge count=ub.count}}
|
{{user-badge badge=ub.badge count=ub.count user=user}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -187,6 +187,58 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.show-badge .badge-user-info {
|
||||||
|
margin-left: 2%;
|
||||||
|
.earned {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
margin-top: 5px;
|
||||||
|
display: block;
|
||||||
|
color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-badge .single-user {
|
||||||
|
margin-left: 2%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
.load-more {
|
||||||
|
padding-top: 30px;
|
||||||
|
display: block;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-badge .single-user .badge-user {
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
margin: 20px 0;
|
||||||
|
.badge-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.post-link {
|
||||||
|
font-size: 1.3em;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
width: 800px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.long-description.banner {
|
.long-description.banner {
|
||||||
width: 88%;
|
width: 88%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
class UserBadgesController < ApplicationController
|
class UserBadgesController < ApplicationController
|
||||||
def index
|
def index
|
||||||
params.permit [:granted_before, :offset]
|
params.permit [:granted_before, :offset, :username]
|
||||||
|
|
||||||
badge = fetch_badge_from_params
|
badge = fetch_badge_from_params
|
||||||
user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(96)
|
user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(96)
|
||||||
user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic)
|
user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic)
|
||||||
|
|
||||||
|
grant_count = nil
|
||||||
|
|
||||||
|
if params[:username]
|
||||||
|
user_id = User.where(username_lower: params[:username].downcase).pluck(:id).first
|
||||||
|
user_badges = user_badges.where(user_id: user_id) if user_id
|
||||||
|
grant_count = user_badges.count
|
||||||
|
end
|
||||||
|
|
||||||
if offset = params[:offset]
|
if offset = params[:offset]
|
||||||
user_badges = user_badges.offset(offset.to_i)
|
user_badges = user_badges.offset(offset.to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
render_serialized(user_badges, UserBadgeSerializer, root: "user_badges", include_long_description: true)
|
user_badges = UserBadges.new(user_badges: user_badges,
|
||||||
|
username: params[:username],
|
||||||
|
grant_count: grant_count)
|
||||||
|
|
||||||
|
render_serialized(user_badges, UserBadgesSerializer, root: :user_badge_info, include_long_description: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def username
|
def username
|
||||||
|
@ -28,7 +40,7 @@ class UserBadgesController < ApplicationController
|
||||||
.includes(post: :topic)
|
.includes(post: :topic)
|
||||||
.includes(:granted_by)
|
.includes(:granted_by)
|
||||||
|
|
||||||
render_serialized(user_badges, DetailedUserBadgeSerializer, root: "user_badges")
|
render_serialized(user_badges, DetailedUserBadgeSerializer, root: :user_badges)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
12
app/models/user_badges.rb
Normal file
12
app/models/user_badges.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# view model for user badges
|
||||||
|
class UserBadges
|
||||||
|
alias :read_attribute_for_serialization :send
|
||||||
|
|
||||||
|
attr_accessor :user_badges, :username, :grant_count
|
||||||
|
|
||||||
|
def initialize(opts={})
|
||||||
|
@user_badges = opts[:user_badges]
|
||||||
|
@username = opts[:username]
|
||||||
|
@grant_count = opts[:grant_count]
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,14 @@
|
||||||
class UserBadgeSerializer < ApplicationSerializer
|
class UserBadgeSerializer < ApplicationSerializer
|
||||||
|
|
||||||
|
class UserSerializer < BasicUserSerializer
|
||||||
|
attributes :name, :moderator, :admin
|
||||||
|
end
|
||||||
|
|
||||||
attributes :id, :granted_at, :count, :post_id, :post_number
|
attributes :id, :granted_at, :count, :post_id, :post_number
|
||||||
|
|
||||||
has_one :badge
|
has_one :badge
|
||||||
has_one :user, serializer: BasicUserSerializer, root: :users
|
has_one :user, serializer: UserSerializer, root: :users
|
||||||
has_one :granted_by, serializer: BasicUserSerializer, root: :users
|
has_one :granted_by, serializer: UserSerializer, root: :users
|
||||||
has_one :topic, serializer: BasicTopicSerializer
|
has_one :topic, serializer: BasicTopicSerializer
|
||||||
|
|
||||||
def include_count?
|
def include_count?
|
||||||
|
|
4
app/serializers/user_badges_serializer.rb
Normal file
4
app/serializers/user_badges_serializer.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
class UserBadgesSerializer < ApplicationSerializer
|
||||||
|
has_many :user_badges, embed: :objects
|
||||||
|
attributes :grant_count, :username
|
||||||
|
end
|
|
@ -2746,6 +2746,10 @@ en:
|
||||||
mark_watching: '<b>m</b>, <b>w</b> Watch topic'
|
mark_watching: '<b>m</b>, <b>w</b> Watch topic'
|
||||||
|
|
||||||
badges:
|
badges:
|
||||||
|
earned_n_times:
|
||||||
|
one: "Earned this badge 1 time"
|
||||||
|
other: "Earned this badge %{count} times"
|
||||||
|
more_with_badge: "Others with this badge"
|
||||||
title: Badges
|
title: Badges
|
||||||
allow_title: "can be used as a title"
|
allow_title: "can be used as a title"
|
||||||
multiple_grant: "can be awarded multiple times"
|
multiple_grant: "can be awarded multiple times"
|
||||||
|
|
|
@ -12,9 +12,11 @@ describe UserBadgesController do
|
||||||
|
|
||||||
xhr :get, :index, badge_id: badge.id
|
xhr :get, :index, badge_id: badge.id
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
parsed = JSON.parse(response.body)
|
parsed = JSON.parse(response.body)
|
||||||
expect(parsed["topics"]).to eq(nil)
|
expect(parsed["topics"]).to eq(nil)
|
||||||
expect(parsed["user_badges"][0]["post_id"]).to eq(nil)
|
expect(parsed["badges"].length).to eq(1)
|
||||||
|
expect(parsed["user_badge_info"]["user_badges"][0]["post_id"]).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ describe UserBadgesController do
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
parsed = JSON.parse(response.body)
|
parsed = JSON.parse(response.body)
|
||||||
expect(parsed["user_badges"].length).to eq(1)
|
expect(parsed["user_badge_info"]["user_badges"].length).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes counts when passed the aggregate argument' do
|
it 'includes counts when passed the aggregate argument' do
|
||||||
|
|
Reference in a new issue