mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
FEATURE: allow advanced badge options in admin screen
clean up serializer, allow simplistic preview
This commit is contained in:
parent
469e74316b
commit
ec03d135fa
14 changed files with 206 additions and 32 deletions
|
@ -8,8 +8,6 @@
|
|||
@module Discourse
|
||||
**/
|
||||
|
||||
var RESERVED_BADGE_COUNT = 100;
|
||||
|
||||
Discourse.AdminBadgeController = Discourse.ObjectController.extend({
|
||||
/**
|
||||
Whether this badge has been selected.
|
||||
|
@ -33,5 +31,5 @@ Discourse.AdminBadgeController = Discourse.ObjectController.extend({
|
|||
@property readOnly
|
||||
@type {Boolean}
|
||||
**/
|
||||
readOnly: Ember.computed.lt('model.id', RESERVED_BADGE_COUNT)
|
||||
readOnly: Ember.computed.alias('model.system')
|
||||
});
|
||||
|
|
|
@ -77,6 +77,21 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
|
|||
|
||||
actions: {
|
||||
|
||||
preview: function(badge) {
|
||||
// TODO wire modal and localize
|
||||
Discourse.ajax('/admin/badges/preview.json', {
|
||||
method: 'post',
|
||||
data: {sql: badge.query, target_posts: !!badge.target_posts}
|
||||
}).then(function(json){
|
||||
if(json.error){
|
||||
bootbox.alert(json.error);
|
||||
} else {
|
||||
bootbox.alert(json.grant_count + " badges to be assigned");
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
Create a new badge and select it.
|
||||
|
||||
|
@ -107,7 +122,21 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
|
|||
**/
|
||||
save: function() {
|
||||
if (!this.get('disableSave')) {
|
||||
this.get('selectedItem').save();
|
||||
var fields = ['allow_title', 'multiple_grant',
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts', 'name', 'description',
|
||||
'icon', 'query', 'badge_grouping_id',
|
||||
'trigger'];
|
||||
|
||||
if(this.get('selectedItem.system')){
|
||||
var protectedFields = this.get('protectedSystemFields');
|
||||
fields = _.filter(fields, function(f){
|
||||
return !_.include(protectedFields,f);
|
||||
});
|
||||
}
|
||||
|
||||
this.get('selectedItem').save(fields);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
Discourse.AdminBadgesRoute = Discourse.Route.extend({
|
||||
|
||||
model: function() {
|
||||
return Discourse.Badge.findAll();
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
// TODO build into findAll
|
||||
Discourse.ajax('/admin/badges/groupings').then(function(json) {
|
||||
setupController: function(controller) {
|
||||
Discourse.ajax('/admin/badges.json').then(function(json){
|
||||
controller.set('badgeGroupings', json.badge_groupings);
|
||||
});
|
||||
Discourse.ajax('/admin/badges/types').then(function(json) {
|
||||
controller.set('badgeTypes', json.badge_types);
|
||||
controller.set('protectedSystemFields', json.admin_badges.protected_system_fields);
|
||||
var triggers = [];
|
||||
_.each(json.admin_badges.triggers,function(v,k){
|
||||
triggers.push({id: v, name: I18n.t('admin.badges.trigger_type.'+k)});
|
||||
});
|
||||
controller.set('badgeTriggers', triggers);
|
||||
controller.set('model', Discourse.Badge.createFromJson(json));
|
||||
});
|
||||
controller.set('model', model);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -52,10 +52,10 @@
|
|||
{{view Ember.Select name="badge_grouping_id" value=badge_grouping_id
|
||||
content=controller.badgeGroupings
|
||||
optionValuePath="content.id"
|
||||
optionLabelPath="content.name"
|
||||
disabled=readOnly}}
|
||||
optionLabelPath="content.name"}}
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label for="description">{{i18n admin.badges.description}}</label>
|
||||
{{#if controller.canEditDescription}}
|
||||
|
@ -65,6 +65,40 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="query">{{i18n admin.badges.query}}</label>
|
||||
{{textarea name="query" value=query disabled=readOnly}}
|
||||
</div>
|
||||
|
||||
{{#if hasQuery}}
|
||||
|
||||
<a href="/admin/badges/preview" {{action preview this}}>{{i18n admin.badges.preview}}</a>
|
||||
|
||||
<div>
|
||||
<span>
|
||||
{{input type="checkbox" checked=auto_revoke disabled=readOnly}}
|
||||
{{i18n admin.badges.auto_revoke}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>
|
||||
{{input type="checkbox" checked=target_posts disabled=readOnly}}
|
||||
{{i18n admin.badges.target_posts}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="trigger">{{i18n admin.badges.trigger}}</label>
|
||||
{{view Ember.Select name="trigger" value=trigger
|
||||
content=controller.badgeTriggers
|
||||
optionValuePath="content.id"
|
||||
optionLabelPath="content.name"
|
||||
disabled=readOnly}}
|
||||
</div>
|
||||
|
||||
{{/if}}
|
||||
|
||||
<div>
|
||||
<span>
|
||||
{{input type="checkbox" checked=allow_title disabled=readOnly}}
|
||||
|
@ -86,6 +120,13 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>
|
||||
{{input type="checkbox" checked=show_posts disabled=readOnly}}
|
||||
{{i18n admin.badges.show_posts}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>
|
||||
{{input type="checkbox" checked=enabled}}
|
||||
|
|
|
@ -15,6 +15,11 @@ Discourse.Badge = Discourse.Model.extend({
|
|||
**/
|
||||
newBadge: Em.computed.none('id'),
|
||||
|
||||
hasQuery: function(){
|
||||
var query = this.get('query');
|
||||
return query && query.trim().length > 0;
|
||||
}.property('query'),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
|
@ -100,7 +105,7 @@ Discourse.Badge = Discourse.Model.extend({
|
|||
@method save
|
||||
@returns {Promise} A promise that resolves to the updated `Discourse.Badge`
|
||||
**/
|
||||
save: function() {
|
||||
save: function(fields) {
|
||||
this.set('savingStatus', I18n.t('saving'));
|
||||
this.set('saving', true);
|
||||
|
||||
|
@ -114,19 +119,23 @@ Discourse.Badge = Discourse.Model.extend({
|
|||
requestType = "PUT";
|
||||
}
|
||||
|
||||
var boolFields = ['allow_title', 'multiple_grant',
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts' ];
|
||||
|
||||
var data = {};
|
||||
fields.forEach(function(field){
|
||||
var d = self.get(field);
|
||||
if(_.include(boolFields, field)) {
|
||||
d = !!d;
|
||||
}
|
||||
data[field] = d;
|
||||
});
|
||||
|
||||
return Discourse.ajax(url, {
|
||||
type: requestType,
|
||||
data: {
|
||||
name: this.get('name'),
|
||||
description: this.get('description'),
|
||||
badge_type_id: this.get('badge_type_id'),
|
||||
allow_title: !!this.get('allow_title'),
|
||||
multiple_grant: !!this.get('multiple_grant'),
|
||||
listable: !!this.get('listable'),
|
||||
enabled: !!this.get('enabled'),
|
||||
icon: this.get('icon'),
|
||||
badge_grouping_id: this.get('badge_grouping_id')
|
||||
}
|
||||
data: data
|
||||
}).then(function(json) {
|
||||
self.updateFromJson(json);
|
||||
self.set('savingStatus', I18n.t('saved'));
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
class Admin::BadgesController < Admin::AdminController
|
||||
|
||||
def index
|
||||
data = {
|
||||
badge_types: BadgeType.all.to_a,
|
||||
badge_groupings: BadgeGrouping.all.to_a,
|
||||
badges: Badge.all.to_a,
|
||||
protected_system_fields: Badge.protected_system_fields,
|
||||
triggers: Badge.trigger_hash
|
||||
}
|
||||
render_serialized(OpenStruct.new(data), AdminBadgesSerializer)
|
||||
end
|
||||
|
||||
def preview
|
||||
render json: BadgeGranter.preview(params[:sql], target_posts: params[:target_posts] == "true")
|
||||
end
|
||||
|
||||
def badge_types
|
||||
badge_types = BadgeType.all.to_a
|
||||
render_serialized(badge_types, BadgeTypeSerializer, root: "badge_types")
|
||||
|
@ -35,7 +51,9 @@ class Admin::BadgesController < Admin::AdminController
|
|||
end
|
||||
|
||||
def update_badge_from_params(badge)
|
||||
allowed = [:icon, :name, :description, :badge_type_id, :allow_title, :multiple_grant, :listable, :enabled, :badge_grouping_id]
|
||||
allowed = Badge.column_names.map(&:to_sym)
|
||||
allowed -= [:id, :created_at, :updated_at, :grant_count]
|
||||
allowed -= Badge.protected_system_fields if badge.system?
|
||||
params.permit(*allowed)
|
||||
|
||||
allowed.each do |key|
|
||||
|
|
|
@ -17,7 +17,16 @@ class Badge < ActiveRecord::Base
|
|||
# other consts
|
||||
AutobiographerMinBioLength = 10
|
||||
|
||||
def self.trigger_hash
|
||||
Hash[*(
|
||||
Badge::Trigger.constants.map{|k|
|
||||
[k.to_s.underscore, Badge::Trigger.const_get(k)]
|
||||
}.flatten
|
||||
)]
|
||||
end
|
||||
|
||||
module Trigger
|
||||
None = 0
|
||||
PostAction = 1
|
||||
PostRevision = 2
|
||||
TrustLevelChange = 4
|
||||
|
@ -169,6 +178,11 @@ SQL
|
|||
|
||||
scope :enabled, ->{ where(enabled: true) }
|
||||
|
||||
# fields that can not be edited on system badges
|
||||
def self.protected_system_fields
|
||||
[:badge_type_id, :multiple_grant, :target_posts, :show_posts, :query, :trigger, :auto_revoke, :listable]
|
||||
end
|
||||
|
||||
|
||||
def self.trust_level_badge_ids
|
||||
(1..4).to_a
|
||||
|
@ -191,6 +205,10 @@ SQL
|
|||
!self.multiple_grant?
|
||||
end
|
||||
|
||||
def system?
|
||||
id < 100
|
||||
end
|
||||
|
||||
def default_name=(val)
|
||||
self.name ||= val
|
||||
end
|
||||
|
@ -224,6 +242,7 @@ end
|
|||
# auto_revoke :boolean default(TRUE), not null
|
||||
# badge_grouping_id :integer default(5), not null
|
||||
# trigger :integer
|
||||
# show_posts :boolean default(FALSE), not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
3
app/serializers/admin_badge_serializer.rb
Normal file
3
app/serializers/admin_badge_serializer.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class AdminBadgeSerializer < BadgeSerializer
|
||||
attributes :query, :trigger, :target_posts, :auto_revoke, :show_posts
|
||||
end
|
14
app/serializers/admin_badges_serializer.rb
Normal file
14
app/serializers/admin_badges_serializer.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class AdminBadgesSerializer < ApplicationSerializer
|
||||
attributes :protected_system_fields, :triggers
|
||||
has_many :badges, serializer: AdminBadgeSerializer
|
||||
has_many :badge_groupings
|
||||
has_many :badge_types
|
||||
|
||||
def protected_system_fields
|
||||
object.protected_system_fields
|
||||
end
|
||||
|
||||
def triggers
|
||||
object.triggers
|
||||
end
|
||||
end
|
|
@ -1,5 +1,10 @@
|
|||
class BadgeSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :description, :grant_count, :allow_title,
|
||||
:multiple_grant, :icon, :listable, :enabled, :badge_grouping_id
|
||||
:multiple_grant, :icon, :listable, :enabled, :badge_grouping_id,
|
||||
:system
|
||||
has_one :badge_type
|
||||
|
||||
def system
|
||||
object.system?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -122,6 +122,32 @@ class BadgeGranter
|
|||
"badge_queue".freeze
|
||||
end
|
||||
|
||||
def self.preview(sql, opts = {})
|
||||
count_sql = "SELECT COUNT(*) count FROM (#{sql}) q"
|
||||
grant_count = SqlBuilder.map_exec(OpenStruct, count_sql).first.count
|
||||
|
||||
grants_sql =
|
||||
if opts[:target_posts]
|
||||
"SELECT u.id, u.username, q.post_id, t.title, q.granted_at
|
||||
FROM(#{sql}) q
|
||||
JOIN users u on u.id = q.user_id
|
||||
LEFT JOIN badge_posts p on p.id = q.post_id
|
||||
LEFT JOIN topics t on t.id = q.topic_id
|
||||
LIMIT 10"
|
||||
else
|
||||
"SELECT u.id, u.username, q.granted_at
|
||||
FROM(#{sql}) q
|
||||
JOIN users u on u.id = q.user_id
|
||||
LIMIT 10"
|
||||
end
|
||||
|
||||
sample = SqlBuilder.map_exec(OpenStruct, grants_sql).map(&:to_h)
|
||||
|
||||
{grant_count: grant_count, sample: sample}
|
||||
rescue => e
|
||||
{error: e.to_s}
|
||||
end
|
||||
|
||||
def self.backfill(badge, opts=nil)
|
||||
return unless badge.query.present? && badge.enabled
|
||||
|
||||
|
|
|
@ -1907,6 +1907,18 @@ en:
|
|||
listable: Show badge on the public badges page
|
||||
enabled: Enable badge
|
||||
icon: Icon
|
||||
query: Badge Query (SQL)
|
||||
target_posts: Query targets posts
|
||||
auto_revoke: Run revocation query daily
|
||||
show_posts: Show post granting badge on badge page
|
||||
preview: Preview badge
|
||||
trigger: Trigger
|
||||
trigger_type:
|
||||
none: "Update daily"
|
||||
post_action: "When a user acts on post"
|
||||
post_revision: "When a user edits or creates a post"
|
||||
trust_level_change: "When a user changes trust level"
|
||||
user_change: "When a user is edited or created"
|
||||
|
||||
lightbox:
|
||||
download: "download"
|
||||
|
|
|
@ -147,6 +147,7 @@ Discourse::Application.routes.draw do
|
|||
collection do
|
||||
get "types" => "badges#badge_types"
|
||||
get "groupings" => "badges#badge_groupings"
|
||||
post "preview" => "badges#preview"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@ test('updateFromJson', function() {
|
|||
test('save', function() {
|
||||
this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({}));
|
||||
var badge = Discourse.Badge.create({name: "New Badge", description: "This is a new badge.", badge_type_id: 1});
|
||||
badge.save();
|
||||
// TODO: clean API
|
||||
badge.save(["name", "description", "badge_type_id"]);
|
||||
ok(Discourse.ajax.calledOnce, "saved badge");
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue