FEATURE: Warn a user when they have few likes remaining
This commit is contained in:
parent
1fba835d4f
commit
5d4ee2ca1d
|
@ -66,6 +66,10 @@ Discourse.Ajax = Em.Mixin.create({
|
|||
});
|
||||
}
|
||||
|
||||
if (args.returnXHR) {
|
||||
data = { result: data, xhr };
|
||||
}
|
||||
|
||||
Ember.run(null, resolve, data);
|
||||
};
|
||||
|
||||
|
|
|
@ -18,10 +18,7 @@ export default RestModel.extend({
|
|||
},
|
||||
|
||||
togglePromise(post) {
|
||||
if (!this.get('acted')) {
|
||||
return this.act(post).then(() => true);
|
||||
}
|
||||
return this.undo(post).then(() => false);
|
||||
return this.get('acted') ? this.undo(post) : this.act(post);
|
||||
},
|
||||
|
||||
toggle(post) {
|
||||
|
@ -64,11 +61,15 @@ export default RestModel.extend({
|
|||
message: opts.message,
|
||||
take_action: opts.takeAction,
|
||||
flag_topic: this.get('flagTopic') ? true : false
|
||||
}
|
||||
}).then(function(result) {
|
||||
},
|
||||
returnXHR: true,
|
||||
}).then(function(data) {
|
||||
if (!self.get('flagTopic')) {
|
||||
return post.updateActionsSummary(result);
|
||||
post.updateActionsSummary(data.result);
|
||||
}
|
||||
const remaining = parseInt(data.xhr.getResponseHeader('Discourse-Actions-Remaining') || 0);
|
||||
const max = parseInt(data.xhr.getResponseHeader('Discourse-Actions-Max') || 0);
|
||||
return { acted: true, remaining, max };
|
||||
}).catch(function(error) {
|
||||
popupAjaxError(error);
|
||||
self.removeAction(post);
|
||||
|
@ -83,7 +84,10 @@ export default RestModel.extend({
|
|||
return Discourse.ajax("/post_actions/" + post.get('id'), {
|
||||
type: 'DELETE',
|
||||
data: { post_action_type_id: this.get('id') }
|
||||
}).then(result => post.updateActionsSummary(result));
|
||||
}).then(result => {
|
||||
post.updateActionsSummary(result);
|
||||
return { acted: false };
|
||||
});
|
||||
},
|
||||
|
||||
deferFlags(post) {
|
||||
|
|
|
@ -423,7 +423,27 @@ export default createWidget('post', {
|
|||
const likeAction = post.get('likeAction');
|
||||
|
||||
if (likeAction && likeAction.get('canToggle')) {
|
||||
return likeAction.togglePromise(post);
|
||||
return likeAction.togglePromise(post).then(result => this._warnIfClose(result));
|
||||
}
|
||||
},
|
||||
|
||||
_warnIfClose(result) {
|
||||
if (!result || !result.acted) { return; }
|
||||
|
||||
const kvs = this.keyValueStore;
|
||||
const lastWarnedLikes = kvs.get('lastWarnedLikes');
|
||||
|
||||
// only warn once per day
|
||||
const yesterday = new Date().getTime() - 1000 * 60 * 60 * 24;
|
||||
if (lastWarnedLikes && parseInt(lastWarnedLikes) > yesterday) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { remaining, max } = result;
|
||||
const threshold = Math.ceil(max * 0.1);
|
||||
if (remaining === threshold) {
|
||||
bootbox.alert(I18n.t('post.few_likes_left'));
|
||||
kvs.set({ key: 'lastWarnedLikes', value: new Date().getTime() });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -119,6 +119,7 @@ export default class Widget {
|
|||
this.currentUser = container.lookup('current-user:main');
|
||||
this.store = container.lookup('store:main');
|
||||
this.appEvents = container.lookup('app-events:main');
|
||||
this.keyValueStore = container.lookup('key-value-store:main');
|
||||
|
||||
if (this.name) {
|
||||
const custom = _customSettings[this.name];
|
||||
|
|
|
@ -21,6 +21,12 @@ class PostActionsController < ApplicationController
|
|||
else
|
||||
# We need to reload or otherwise we are showing the old values on the front end
|
||||
@post.reload
|
||||
|
||||
if @post_action_type_id == PostActionType.types[:like]
|
||||
limiter = post_action.post_action_rate_limiter
|
||||
response.headers['Discourse-Actions-Remaining'] = limiter.remaining.to_s
|
||||
response.headers['Discourse-Actions-Max'] = limiter.max.to_s
|
||||
end
|
||||
render_post_json(@post, _add_raw = false)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1511,6 +1511,8 @@ en:
|
|||
archetypes:
|
||||
save: 'Save Options'
|
||||
|
||||
few_likes_left: "Warning: You have few likes left to give today. Use them wisely!"
|
||||
|
||||
controls:
|
||||
reply: "begin composing a reply to this post"
|
||||
like: "like this post"
|
||||
|
|
|
@ -67,6 +67,15 @@ class RateLimiter
|
|||
$redis.lpop(@key)
|
||||
end
|
||||
|
||||
def remaining
|
||||
return @max if @user && @user.staff?
|
||||
|
||||
arr = $redis.lrange(@key, 0, @max) || []
|
||||
t0 = Time.now.to_i
|
||||
arr.reject! {|a| (t0 - a.to_i) > @secs}
|
||||
@max - arr.size
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def seconds_to_wait
|
||||
|
|
|
@ -39,6 +39,16 @@ describe RateLimiter do
|
|||
end
|
||||
end
|
||||
|
||||
context "remaining" do
|
||||
it "updates correctly" do
|
||||
expect(rate_limiter.remaining).to eq(2)
|
||||
rate_limiter.performed!
|
||||
expect(rate_limiter.remaining).to eq(1)
|
||||
rate_limiter.performed!
|
||||
expect(rate_limiter.remaining).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "multiple calls" do
|
||||
before do
|
||||
rate_limiter.performed!
|
||||
|
@ -47,6 +57,7 @@ describe RateLimiter do
|
|||
|
||||
it "returns false for can_perform when the limit has been hit" do
|
||||
expect(rate_limiter.can_perform?).to eq(false)
|
||||
expect(rate_limiter.remaining).to eq(0)
|
||||
end
|
||||
|
||||
it "raises an error the third time called" do
|
||||
|
@ -54,10 +65,10 @@ describe RateLimiter do
|
|||
end
|
||||
|
||||
context "as an admin/moderator" do
|
||||
|
||||
it "returns true for can_perform if the user is an admin" do
|
||||
user.admin = true
|
||||
expect(rate_limiter.can_perform?).to eq(true)
|
||||
expect(rate_limiter.remaining).to eq(2)
|
||||
end
|
||||
|
||||
it "doesn't raise an error when an admin performs the task" do
|
||||
|
@ -74,8 +85,6 @@ describe RateLimiter do
|
|||
user.moderator = true
|
||||
expect { rate_limiter.performed! }.not_to raise_error
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
context "rollback!" do
|
||||
|
@ -90,7 +99,6 @@ describe RateLimiter do
|
|||
it "raises no error now that there is room" do
|
||||
expect { rate_limiter.performed! }.not_to raise_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -12,6 +12,10 @@ describe PostAction do
|
|||
let(:second_post) { Fabricate(:post, topic_id: post.topic_id) }
|
||||
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) }
|
||||
|
||||
def value_for(user_id, dt)
|
||||
GivenDailyLike.find_for(user_id, dt).pluck(:likes_given)[0] || 0
|
||||
end
|
||||
|
||||
describe "rate limits" do
|
||||
|
||||
it "limits redo/undo" do
|
||||
|
@ -172,7 +176,7 @@ describe PostAction do
|
|||
# we need this to test it
|
||||
TopicUser.change(codinghorror, post.topic, posted: true)
|
||||
|
||||
expect(GivenDailyLike.value_for(moderator.id, Date.today)).to eq(0)
|
||||
expect(value_for(moderator.id, Date.today)).to eq(0)
|
||||
|
||||
PostAction.act(moderator, post, PostActionType.types[:like])
|
||||
PostAction.act(codinghorror, second_post, PostActionType.types[:like])
|
||||
|
@ -180,7 +184,7 @@ describe PostAction do
|
|||
post.topic.reload
|
||||
expect(post.topic.like_count).to eq(2)
|
||||
|
||||
expect(GivenDailyLike.value_for(moderator.id, Date.today)).to eq(1)
|
||||
expect(value_for(moderator.id, Date.today)).to eq(1)
|
||||
|
||||
tu = TopicUser.get(post.topic, codinghorror)
|
||||
expect(tu.liked).to be true
|
||||
|
@ -251,7 +255,7 @@ describe PostAction do
|
|||
expect(post.like_score).to eq(1)
|
||||
post.topic.reload
|
||||
expect(post.topic.like_count).to eq(1)
|
||||
expect(GivenDailyLike.value_for(codinghorror.id, Date.today)).to eq(1)
|
||||
expect(value_for(codinghorror.id, Date.today)).to eq(1)
|
||||
|
||||
# When a staff member likes it
|
||||
PostAction.act(moderator, post, PostActionType.types[:like])
|
||||
|
@ -264,7 +268,7 @@ describe PostAction do
|
|||
post.reload
|
||||
expect(post.like_count).to eq(1)
|
||||
expect(post.like_score).to eq(3)
|
||||
expect(GivenDailyLike.value_for(codinghorror.id, Date.today)).to eq(0)
|
||||
expect(value_for(codinghorror.id, Date.today)).to eq(0)
|
||||
|
||||
PostAction.remove_act(moderator, post, PostActionType.types[:like])
|
||||
post.reload
|
||||
|
|
Loading…
Reference in New Issue