mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-02-17 04:01:29 -05:00
FEATURE: Warn a user when they have few likes remaining
This commit is contained in:
parent
1fba835d4f
commit
5d4ee2ca1d
9 changed files with 75 additions and 17 deletions
|
@ -66,6 +66,10 @@ Discourse.Ajax = Em.Mixin.create({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.returnXHR) {
|
||||||
|
data = { result: data, xhr };
|
||||||
|
}
|
||||||
|
|
||||||
Ember.run(null, resolve, data);
|
Ember.run(null, resolve, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,7 @@ export default RestModel.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePromise(post) {
|
togglePromise(post) {
|
||||||
if (!this.get('acted')) {
|
return this.get('acted') ? this.undo(post) : this.act(post);
|
||||||
return this.act(post).then(() => true);
|
|
||||||
}
|
|
||||||
return this.undo(post).then(() => false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle(post) {
|
toggle(post) {
|
||||||
|
@ -64,11 +61,15 @@ export default RestModel.extend({
|
||||||
message: opts.message,
|
message: opts.message,
|
||||||
take_action: opts.takeAction,
|
take_action: opts.takeAction,
|
||||||
flag_topic: this.get('flagTopic') ? true : false
|
flag_topic: this.get('flagTopic') ? true : false
|
||||||
}
|
},
|
||||||
}).then(function(result) {
|
returnXHR: true,
|
||||||
|
}).then(function(data) {
|
||||||
if (!self.get('flagTopic')) {
|
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) {
|
}).catch(function(error) {
|
||||||
popupAjaxError(error);
|
popupAjaxError(error);
|
||||||
self.removeAction(post);
|
self.removeAction(post);
|
||||||
|
@ -83,7 +84,10 @@ export default RestModel.extend({
|
||||||
return Discourse.ajax("/post_actions/" + post.get('id'), {
|
return Discourse.ajax("/post_actions/" + post.get('id'), {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
data: { post_action_type_id: this.get('id') }
|
data: { post_action_type_id: this.get('id') }
|
||||||
}).then(result => post.updateActionsSummary(result));
|
}).then(result => {
|
||||||
|
post.updateActionsSummary(result);
|
||||||
|
return { acted: false };
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deferFlags(post) {
|
deferFlags(post) {
|
||||||
|
|
|
@ -423,7 +423,27 @@ export default createWidget('post', {
|
||||||
const likeAction = post.get('likeAction');
|
const likeAction = post.get('likeAction');
|
||||||
|
|
||||||
if (likeAction && likeAction.get('canToggle')) {
|
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.currentUser = container.lookup('current-user:main');
|
||||||
this.store = container.lookup('store:main');
|
this.store = container.lookup('store:main');
|
||||||
this.appEvents = container.lookup('app-events:main');
|
this.appEvents = container.lookup('app-events:main');
|
||||||
|
this.keyValueStore = container.lookup('key-value-store:main');
|
||||||
|
|
||||||
if (this.name) {
|
if (this.name) {
|
||||||
const custom = _customSettings[this.name];
|
const custom = _customSettings[this.name];
|
||||||
|
|
|
@ -21,6 +21,12 @@ class PostActionsController < ApplicationController
|
||||||
else
|
else
|
||||||
# We need to reload or otherwise we are showing the old values on the front end
|
# We need to reload or otherwise we are showing the old values on the front end
|
||||||
@post.reload
|
@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)
|
render_post_json(@post, _add_raw = false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1511,6 +1511,8 @@ en:
|
||||||
archetypes:
|
archetypes:
|
||||||
save: 'Save Options'
|
save: 'Save Options'
|
||||||
|
|
||||||
|
few_likes_left: "Warning: You have few likes left to give today. Use them wisely!"
|
||||||
|
|
||||||
controls:
|
controls:
|
||||||
reply: "begin composing a reply to this post"
|
reply: "begin composing a reply to this post"
|
||||||
like: "like this post"
|
like: "like this post"
|
||||||
|
|
|
@ -67,6 +67,15 @@ class RateLimiter
|
||||||
$redis.lpop(@key)
|
$redis.lpop(@key)
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def seconds_to_wait
|
def seconds_to_wait
|
||||||
|
|
|
@ -39,6 +39,16 @@ describe RateLimiter do
|
||||||
end
|
end
|
||||||
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
|
context "multiple calls" do
|
||||||
before do
|
before do
|
||||||
rate_limiter.performed!
|
rate_limiter.performed!
|
||||||
|
@ -47,6 +57,7 @@ describe RateLimiter do
|
||||||
|
|
||||||
it "returns false for can_perform when the limit has been hit" 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.can_perform?).to eq(false)
|
||||||
|
expect(rate_limiter.remaining).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an error the third time called" do
|
it "raises an error the third time called" do
|
||||||
|
@ -54,10 +65,10 @@ describe RateLimiter do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "as an admin/moderator" do
|
context "as an admin/moderator" do
|
||||||
|
|
||||||
it "returns true for can_perform if the user is an admin" do
|
it "returns true for can_perform if the user is an admin" do
|
||||||
user.admin = true
|
user.admin = true
|
||||||
expect(rate_limiter.can_perform?).to eq(true)
|
expect(rate_limiter.can_perform?).to eq(true)
|
||||||
|
expect(rate_limiter.remaining).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't raise an error when an admin performs the task" do
|
it "doesn't raise an error when an admin performs the task" do
|
||||||
|
@ -74,8 +85,6 @@ describe RateLimiter do
|
||||||
user.moderator = true
|
user.moderator = true
|
||||||
expect { rate_limiter.performed! }.not_to raise_error
|
expect { rate_limiter.performed! }.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "rollback!" do
|
context "rollback!" do
|
||||||
|
@ -90,7 +99,6 @@ describe RateLimiter do
|
||||||
it "raises no error now that there is room" do
|
it "raises no error now that there is room" do
|
||||||
expect { rate_limiter.performed! }.not_to raise_error
|
expect { rate_limiter.performed! }.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,10 @@ describe PostAction do
|
||||||
let(:second_post) { Fabricate(:post, topic_id: post.topic_id) }
|
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) }
|
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
|
describe "rate limits" do
|
||||||
|
|
||||||
it "limits redo/undo" do
|
it "limits redo/undo" do
|
||||||
|
@ -172,7 +176,7 @@ describe PostAction do
|
||||||
# we need this to test it
|
# we need this to test it
|
||||||
TopicUser.change(codinghorror, post.topic, posted: true)
|
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(moderator, post, PostActionType.types[:like])
|
||||||
PostAction.act(codinghorror, second_post, PostActionType.types[:like])
|
PostAction.act(codinghorror, second_post, PostActionType.types[:like])
|
||||||
|
@ -180,7 +184,7 @@ describe PostAction do
|
||||||
post.topic.reload
|
post.topic.reload
|
||||||
expect(post.topic.like_count).to eq(2)
|
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)
|
tu = TopicUser.get(post.topic, codinghorror)
|
||||||
expect(tu.liked).to be true
|
expect(tu.liked).to be true
|
||||||
|
@ -251,7 +255,7 @@ describe PostAction do
|
||||||
expect(post.like_score).to eq(1)
|
expect(post.like_score).to eq(1)
|
||||||
post.topic.reload
|
post.topic.reload
|
||||||
expect(post.topic.like_count).to eq(1)
|
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
|
# When a staff member likes it
|
||||||
PostAction.act(moderator, post, PostActionType.types[:like])
|
PostAction.act(moderator, post, PostActionType.types[:like])
|
||||||
|
@ -264,7 +268,7 @@ describe PostAction do
|
||||||
post.reload
|
post.reload
|
||||||
expect(post.like_count).to eq(1)
|
expect(post.like_count).to eq(1)
|
||||||
expect(post.like_score).to eq(3)
|
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])
|
PostAction.remove_act(moderator, post, PostActionType.types[:like])
|
||||||
post.reload
|
post.reload
|
||||||
|
|
Loading…
Reference in a new issue