mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
UX: Nicer selection of suspend duration
This commit is contained in:
parent
677b016387
commit
6bce3004d9
@ -3,40 +3,37 @@ import computed from 'ember-addons/ember-computed-decorators';
|
|||||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export default Ember.Controller.extend(ModalFunctionality, {
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
duration: null,
|
suspendUntil: null,
|
||||||
reason: null,
|
reason: null,
|
||||||
message: null,
|
message: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
duration: null,
|
suspendUntil: null,
|
||||||
reason: null,
|
reason: null,
|
||||||
message: null,
|
message: null,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('reason', 'loading')
|
@computed('suspendUntil', 'reason', 'loading')
|
||||||
submitDisabled(reason, loading) {
|
submitDisabled(suspendUntil, reason, loading) {
|
||||||
return (loading || !reason || reason.length < 1);
|
return (loading || Ember.isEmpty(suspendUntil) || !reason || reason.length < 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
suspend() {
|
suspend() {
|
||||||
if (this.get('submitDisabled')) { return; }
|
if (this.get('submitDisabled')) { return; }
|
||||||
|
|
||||||
let duration = parseInt(this.get('duration'), 10);
|
this.set('loading', true);
|
||||||
if (duration > 0) {
|
this.get('model').suspend({
|
||||||
this.set('loading', true);
|
suspend_until: this.get('suspendUntil'),
|
||||||
this.get('model').suspend({
|
reason: this.get('reason'),
|
||||||
duration,
|
message: this.get('message')
|
||||||
reason: this.get('reason'),
|
}).then(() => {
|
||||||
message: this.get('message')
|
this.send('closeModal');
|
||||||
}).then(() => {
|
}).catch(popupAjaxError).finally(() => this.set('loading', false));
|
||||||
this.send('closeModal');
|
|
||||||
}).catch(popupAjaxError).finally(() => this.set('loading', false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
{{#d-modal-body title="admin.user.suspend_modal_title"}}
|
{{#d-modal-body title="admin.user.suspend_modal_title"}}
|
||||||
<div class='duration-controls'>
|
<div class='until-controls'>
|
||||||
<label>
|
<label>
|
||||||
{{i18n 'admin.user.suspend_duration'}}
|
{{auto-update-input
|
||||||
{{text-field value=duration maxlength="5" autofocus="autofocus" class="suspend-duration"}}
|
class="suspend-until"
|
||||||
{{i18n 'admin.user.suspend_duration_units'}}
|
label="admin.user.suspend_duration"
|
||||||
|
input=suspendUntil}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -298,7 +298,12 @@
|
|||||||
|
|
||||||
<div class="user-suspended display-row {{if model.isSuspended 'highlight-danger'}}">
|
<div class="user-suspended display-row {{if model.isSuspended 'highlight-danger'}}">
|
||||||
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
|
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
|
||||||
<div class='value'>{{i18n-yes-no model.isSuspended}}</div>
|
<div class='value'>
|
||||||
|
{{i18n-yes-no model.isSuspended}}
|
||||||
|
{{#if model.isSuspended}}
|
||||||
|
{{i18n "admin.user.suspended_until" until=model.suspendedTillDate}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
<div class='controls'>
|
<div class='controls'>
|
||||||
{{#if model.isSuspended}}
|
{{#if model.isSuspended}}
|
||||||
{{d-button
|
{{d-button
|
||||||
@ -306,7 +311,6 @@
|
|||||||
action=(action "unsuspend")
|
action=(action "unsuspend")
|
||||||
icon="ban"
|
icon="ban"
|
||||||
label="admin.user.unsuspend"}}
|
label="admin.user.unsuspend"}}
|
||||||
{{suspendDuration}}
|
|
||||||
{{i18n 'admin.user.suspended_explanation'}}
|
{{i18n 'admin.user.suspended_explanation'}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if model.canSuspend}}
|
{{#if model.canSuspend}}
|
||||||
|
@ -124,7 +124,7 @@ export default Ember.Component.extend(bufferedRender({
|
|||||||
if (val && val.length && castInteger) {
|
if (val && val.length && castInteger) {
|
||||||
val = parseInt(val, 10);
|
val = parseInt(val, 10);
|
||||||
}
|
}
|
||||||
this.set('value', val);
|
Ember.run(() => this.set('value', val));
|
||||||
});
|
});
|
||||||
|
|
||||||
Ember.run.scheduleOnce('afterRender', this, this._triggerChange);
|
Ember.run.scheduleOnce('afterRender', this, this._triggerChange);
|
||||||
|
@ -13,6 +13,7 @@ export default Ember.Component.extend({
|
|||||||
time: null,
|
time: null,
|
||||||
isCustom: Ember.computed.equal('selection', PICK_DATE_AND_TIME),
|
isCustom: Ember.computed.equal('selection', PICK_DATE_AND_TIME),
|
||||||
isBasedOnLastPost: Ember.computed.equal('selection', SET_BASED_ON_LAST_POST),
|
isBasedOnLastPost: Ember.computed.equal('selection', SET_BASED_ON_LAST_POST),
|
||||||
|
displayLabel: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super();
|
this._super();
|
||||||
@ -64,6 +65,10 @@ export default Ember.Component.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this.set('displayLabel', I18n.t(this.get('label') || 'topic.topic_status_update.when'));
|
||||||
|
},
|
||||||
|
|
||||||
@computed("statusType", "input", "isCustom", "date", "time", "willCloseImmediately", "categoryId")
|
@computed("statusType", "input", "isCustom", "date", "time", "willCloseImmediately", "categoryId")
|
||||||
showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately, categoryId) {
|
showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately, categoryId) {
|
||||||
if (!statusType || willCloseImmediately) return false;
|
if (!statusType || willCloseImmediately) return false;
|
||||||
|
@ -25,5 +25,3 @@ registerUnbound('format-date', function(val, params) {
|
|||||||
return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
|
return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="auto-update-input">
|
<div class="auto-update-input">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{i18n "topic.topic_status_update.when"}}</label>
|
<label>{{displayLabel}}</label>
|
||||||
|
|
||||||
{{auto-update-input-selector
|
{{auto-update-input-selector
|
||||||
valueAttribute="id"
|
valueAttribute="id"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.suspend-user-modal {
|
.suspend-user-modal {
|
||||||
|
|
||||||
.duration-controls {
|
.until-controls {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class Admin::UsersController < Admin::AdminController
|
|||||||
|
|
||||||
def suspend
|
def suspend
|
||||||
guardian.ensure_can_suspend!(@user)
|
guardian.ensure_can_suspend!(@user)
|
||||||
@user.suspended_till = params[:duration].to_i.days.from_now
|
@user.suspended_till = params[:suspend_until]
|
||||||
@user.suspended_at = DateTime.now
|
@user.suspended_at = DateTime.now
|
||||||
|
|
||||||
message = params[:message]
|
message = params[:message]
|
||||||
|
@ -17,6 +17,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||||||
:can_be_deleted,
|
:can_be_deleted,
|
||||||
:can_be_anonymized,
|
:can_be_anonymized,
|
||||||
:suspend_reason,
|
:suspend_reason,
|
||||||
|
:suspended_till,
|
||||||
:primary_group_id,
|
:primary_group_id,
|
||||||
:badge_count,
|
:badge_count,
|
||||||
:warnings_received_count,
|
:warnings_received_count,
|
||||||
|
@ -3264,7 +3264,6 @@ en:
|
|||||||
suspend_failed: "Something went wrong suspending this user {{error}}"
|
suspend_failed: "Something went wrong suspending this user {{error}}"
|
||||||
unsuspend_failed: "Something went wrong unsuspending this user {{error}}"
|
unsuspend_failed: "Something went wrong unsuspending this user {{error}}"
|
||||||
suspend_duration: "How long will the user be suspended for?"
|
suspend_duration: "How long will the user be suspended for?"
|
||||||
suspend_duration_units: "(days)"
|
|
||||||
suspend_reason_label: "Why are you suspending? This text <b>will be visible to everyone</b> on this user's profile page, and will be shown to the user when they try to log in. Keep it short."
|
suspend_reason_label: "Why are you suspending? This text <b>will be visible to everyone</b> on this user's profile page, and will be shown to the user when they try to log in. Keep it short."
|
||||||
suspend_reason_hidden_label: "Why are you suspending? This text will be shown to the user when they try to log in. Keep it short."
|
suspend_reason_hidden_label: "Why are you suspending? This text will be shown to the user when they try to log in. Keep it short."
|
||||||
suspend_reason: "Reason"
|
suspend_reason: "Reason"
|
||||||
@ -3272,6 +3271,7 @@ en:
|
|||||||
suspend_message: "Email Message"
|
suspend_message: "Email Message"
|
||||||
suspend_message_placeholder: "Optionally, provide more information about the suspension and it will be emailed to the user."
|
suspend_message_placeholder: "Optionally, provide more information about the suspension and it will be emailed to the user."
|
||||||
suspended_by: "Suspended by"
|
suspended_by: "Suspended by"
|
||||||
|
suspended_until: "(until %{until})"
|
||||||
delete_all_posts: "Delete all posts"
|
delete_all_posts: "Delete all posts"
|
||||||
|
|
||||||
# keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
|
# keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
|
||||||
|
@ -128,7 +128,7 @@ describe Admin::UsersController do
|
|||||||
put(
|
put(
|
||||||
:suspend,
|
:suspend,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
duration: 10,
|
suspend_until: 5.hours.from_now,
|
||||||
reason: "because I said so",
|
reason: "because I said so",
|
||||||
format: :json
|
format: :json
|
||||||
)
|
)
|
||||||
@ -149,15 +149,14 @@ describe Admin::UsersController do
|
|||||||
:critical_user_email,
|
:critical_user_email,
|
||||||
has_entries(
|
has_entries(
|
||||||
type: :account_suspended,
|
type: :account_suspended,
|
||||||
user_id: user.id,
|
user_id: user.id
|
||||||
message: "long reason"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
put(
|
put(
|
||||||
:suspend,
|
:suspend,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
duration: 10,
|
suspend_until: 10.days.from_now,
|
||||||
reason: "short reason",
|
reason: "short reason",
|
||||||
message: "long reason",
|
message: "long reason",
|
||||||
format: :json
|
format: :json
|
||||||
@ -168,10 +167,12 @@ describe Admin::UsersController do
|
|||||||
expect(log).to be_present
|
expect(log).to be_present
|
||||||
expect(log.details).to match(/short reason/)
|
expect(log.details).to match(/short reason/)
|
||||||
expect(log.details).to match(/long reason/)
|
expect(log.details).to match(/long reason/)
|
||||||
|
end
|
||||||
|
|
||||||
it "also revoke any api keys" do
|
it "also revoke any api keys" do
|
||||||
User.any_instance.expects(:revoke_api_key)
|
User.any_instance.expects(:revoke_api_key)
|
||||||
put :suspend, params: { user_id: evil_trout.id }, format: :json
|
put :suspend, params: { user_id: evil_trout.id }, format: :json
|
||||||
|
expect(log.context).to match(/long reason/)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -197,14 +197,13 @@ describe Jobs::UserEmail do
|
|||||||
it "doesn't send the email if the notification has been seen" do
|
it "doesn't send the email if the notification has been seen" do
|
||||||
notification.update_column(:read, true)
|
notification.update_column(:read, true)
|
||||||
message, err = Jobs::UserEmail.new.message_for_email(
|
message, err = Jobs::UserEmail.new.message_for_email(
|
||||||
user,
|
user,
|
||||||
post,
|
post,
|
||||||
:user_mentioned,
|
:user_mentioned,
|
||||||
notification,
|
notification,
|
||||||
notification.notification_type,
|
notification_type: notification.notification_type,
|
||||||
notification.data_hash,
|
notification_data_hash: notification.data_hash
|
||||||
nil,
|
)
|
||||||
nil)
|
|
||||||
|
|
||||||
expect(message).to eq nil
|
expect(message).to eq nil
|
||||||
expect(err.skipped_reason).to match(/notification.*already/)
|
expect(err.skipped_reason).to match(/notification.*already/)
|
||||||
|
@ -43,12 +43,14 @@ QUnit.test("suspend, then unsuspend a user", assert => {
|
|||||||
|
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default');
|
assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default');
|
||||||
|
find('.suspend-until .combobox').select2('val', 'tomorrow');
|
||||||
|
find('.suspend-until .combobox').trigger('change', 'tomorrow');
|
||||||
});
|
});
|
||||||
fillIn('.suspend-duration', 12);
|
|
||||||
fillIn('.suspend-reason', "for breaking the rules");
|
fillIn('.suspend-reason', "for breaking the rules");
|
||||||
fillIn('.suspend-message', "this is an email reason why");
|
fillIn('.suspend-message', "this is an email reason why");
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
assert.equal(find('.perform-suspend[disabled]').length, 0);
|
assert.equal(find('.perform-suspend[disabled]').length, 0, 'no longer disabled');
|
||||||
});
|
});
|
||||||
click('.perform-suspend');
|
click('.perform-suspend');
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user