UX: Nicer selection of suspend duration

This commit is contained in:
Robin Ward 2017-09-13 16:44:47 -04:00
parent 677b016387
commit 6bce3004d9
14 changed files with 51 additions and 43 deletions

View File

@ -3,40 +3,37 @@ import computed from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(ModalFunctionality, {
duration: null,
suspendUntil: null,
reason: null,
message: null,
loading: false,
onShow() {
this.setProperties({
duration: null,
suspendUntil: null,
reason: null,
message: null,
loading: false
});
},
@computed('reason', 'loading')
submitDisabled(reason, loading) {
return (loading || !reason || reason.length < 1);
@computed('suspendUntil', 'reason', 'loading')
submitDisabled(suspendUntil, reason, loading) {
return (loading || Ember.isEmpty(suspendUntil) || !reason || reason.length < 1);
},
actions: {
suspend() {
if (this.get('submitDisabled')) { return; }
let duration = parseInt(this.get('duration'), 10);
if (duration > 0) {
this.set('loading', true);
this.get('model').suspend({
duration,
reason: this.get('reason'),
message: this.get('message')
}).then(() => {
this.send('closeModal');
}).catch(popupAjaxError).finally(() => this.set('loading', false));
}
this.set('loading', true);
this.get('model').suspend({
suspend_until: this.get('suspendUntil'),
reason: this.get('reason'),
message: this.get('message')
}).then(() => {
this.send('closeModal');
}).catch(popupAjaxError).finally(() => this.set('loading', false));
}
}

View File

@ -1,9 +1,10 @@
{{#d-modal-body title="admin.user.suspend_modal_title"}}
<div class='duration-controls'>
<div class='until-controls'>
<label>
{{i18n 'admin.user.suspend_duration'}}
{{text-field value=duration maxlength="5" autofocus="autofocus" class="suspend-duration"}}
{{i18n 'admin.user.suspend_duration_units'}}
{{auto-update-input
class="suspend-until"
label="admin.user.suspend_duration"
input=suspendUntil}}
</label>
</div>

View File

@ -298,7 +298,12 @@
<div class="user-suspended display-row {{if model.isSuspended 'highlight-danger'}}">
<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'>
{{#if model.isSuspended}}
{{d-button
@ -306,7 +311,6 @@
action=(action "unsuspend")
icon="ban"
label="admin.user.unsuspend"}}
{{suspendDuration}}
{{i18n 'admin.user.suspended_explanation'}}
{{else}}
{{#if model.canSuspend}}

View File

@ -124,7 +124,7 @@ export default Ember.Component.extend(bufferedRender({
if (val && val.length && castInteger) {
val = parseInt(val, 10);
}
this.set('value', val);
Ember.run(() => this.set('value', val));
});
Ember.run.scheduleOnce('afterRender', this, this._triggerChange);

View File

@ -13,6 +13,7 @@ export default Ember.Component.extend({
time: null,
isCustom: Ember.computed.equal('selection', PICK_DATE_AND_TIME),
isBasedOnLastPost: Ember.computed.equal('selection', SET_BASED_ON_LAST_POST),
displayLabel: null,
init() {
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")
showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately, categoryId) {
if (!statusType || willCloseImmediately) return false;

View File

@ -25,5 +25,3 @@ registerUnbound('format-date', function(val, params) {
return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
}
});

View File

@ -1,6 +1,6 @@
<div class="auto-update-input">
<div class="control-group">
<label>{{i18n "topic.topic_status_update.when"}}</label>
<label>{{displayLabel}}</label>
{{auto-update-input-selector
valueAttribute="id"

View File

@ -1,6 +1,6 @@
.suspend-user-modal {
.duration-controls {
.until-controls {
margin-bottom: 1em;
}

View File

@ -53,7 +53,7 @@ class Admin::UsersController < Admin::AdminController
def suspend
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
message = params[:message]

View File

@ -17,6 +17,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
:can_be_deleted,
:can_be_anonymized,
:suspend_reason,
:suspended_till,
:primary_group_id,
:badge_count,
:warnings_received_count,

View File

@ -3264,7 +3264,6 @@ en:
suspend_failed: "Something went wrong suspending this user {{error}}"
unsuspend_failed: "Something went wrong unsuspending this user {{error}}"
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_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"
@ -3272,6 +3271,7 @@ en:
suspend_message: "Email Message"
suspend_message_placeholder: "Optionally, provide more information about the suspension and it will be emailed to the user."
suspended_by: "Suspended by"
suspended_until: "(until %{until})"
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

View File

@ -128,7 +128,7 @@ describe Admin::UsersController do
put(
:suspend,
user_id: user.id,
duration: 10,
suspend_until: 5.hours.from_now,
reason: "because I said so",
format: :json
)
@ -149,15 +149,14 @@ describe Admin::UsersController do
:critical_user_email,
has_entries(
type: :account_suspended,
user_id: user.id,
message: "long reason"
user_id: user.id
)
)
put(
:suspend,
user_id: user.id,
duration: 10,
suspend_until: 10.days.from_now,
reason: "short reason",
message: "long reason",
format: :json
@ -168,10 +167,12 @@ describe Admin::UsersController do
expect(log).to be_present
expect(log.details).to match(/short reason/)
expect(log.details).to match(/long reason/)
end
it "also revoke any api keys" do
User.any_instance.expects(:revoke_api_key)
put :suspend, params: { user_id: evil_trout.id }, format: :json
expect(log.context).to match(/long reason/)
end
end

View File

@ -197,14 +197,13 @@ describe Jobs::UserEmail do
it "doesn't send the email if the notification has been seen" do
notification.update_column(:read, true)
message, err = Jobs::UserEmail.new.message_for_email(
user,
post,
:user_mentioned,
notification,
notification.notification_type,
notification.data_hash,
nil,
nil)
user,
post,
:user_mentioned,
notification,
notification_type: notification.notification_type,
notification_data_hash: notification.data_hash
)
expect(message).to eq nil
expect(err.skipped_reason).to match(/notification.*already/)

View File

@ -43,12 +43,14 @@ QUnit.test("suspend, then unsuspend a user", assert => {
andThen(() => {
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-message', "this is an email reason why");
andThen(() => {
assert.equal(find('.perform-suspend[disabled]').length, 0);
assert.equal(find('.perform-suspend[disabled]').length, 0, 'no longer disabled');
});
click('.perform-suspend');
andThen(() => {