FEATURE: add user status to user preferences (#18532)
This commit is contained in:
parent
231dc10bbd
commit
0fe111e492
|
@ -11,6 +11,7 @@ import getURL from "discourse-common/lib/get-url";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import { next } from "@ember/runloop";
|
import { next } from "@ember/runloop";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend(CanCheckEmails, {
|
export default Controller.extend(CanCheckEmails, {
|
||||||
dialog: service(),
|
dialog: service(),
|
||||||
|
@ -22,16 +23,19 @@ export default Controller.extend(CanCheckEmails, {
|
||||||
"title",
|
"title",
|
||||||
"primary_group_id",
|
"primary_group_id",
|
||||||
"flair_group_id",
|
"flair_group_id",
|
||||||
|
"status",
|
||||||
];
|
];
|
||||||
this.set("revoking", {});
|
this.set("revoking", {});
|
||||||
},
|
},
|
||||||
|
|
||||||
canEditName: setting("enable_names"),
|
canEditName: setting("enable_names"),
|
||||||
|
canSelectUserStatus: setting("enable_user_status"),
|
||||||
canSaveUser: true,
|
canSaveUser: true,
|
||||||
|
|
||||||
newNameInput: null,
|
newNameInput: null,
|
||||||
newTitleInput: null,
|
newTitleInput: null,
|
||||||
newPrimaryGroupInput: null,
|
newPrimaryGroupInput: null,
|
||||||
|
newStatus: null,
|
||||||
|
|
||||||
revoking: null,
|
revoking: null,
|
||||||
|
|
||||||
|
@ -146,6 +150,19 @@ export default Controller.extend(CanCheckEmails, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
showUserStatusModal(status) {
|
||||||
|
showModal("user-status", {
|
||||||
|
title: "user_status.set_custom_status",
|
||||||
|
modalClass: "user-status",
|
||||||
|
model: {
|
||||||
|
status,
|
||||||
|
saveAction: async (s) => this.set("newStatus", s),
|
||||||
|
deleteAction: async () => this.set("newStatus", null),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
save() {
|
save() {
|
||||||
this.set("saved", false);
|
this.set("saved", false);
|
||||||
|
@ -155,6 +172,7 @@ export default Controller.extend(CanCheckEmails, {
|
||||||
title: this.newTitleInput,
|
title: this.newTitleInput,
|
||||||
primary_group_id: this.newPrimaryGroupInput,
|
primary_group_id: this.newPrimaryGroupInput,
|
||||||
flair_group_id: this.newFlairGroupId,
|
flair_group_id: this.newFlairGroupId,
|
||||||
|
status: this.newStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.model
|
return this.model
|
||||||
|
|
|
@ -69,6 +69,7 @@ let userFields = [
|
||||||
"user_notification_schedule",
|
"user_notification_schedule",
|
||||||
"sidebar_category_ids",
|
"sidebar_category_ids",
|
||||||
"sidebar_tag_names",
|
"sidebar_tag_names",
|
||||||
|
"status",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function addSaveableUserField(fieldName) {
|
export function addSaveableUserField(fieldName) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default RestrictedUserRoute.extend({
|
||||||
newTitleInput: user.get("title"),
|
newTitleInput: user.get("title"),
|
||||||
newPrimaryGroupInput: user.get("primary_group_id"),
|
newPrimaryGroupInput: user.get("primary_group_id"),
|
||||||
newFlairGroupId: user.get("flair_group_id"),
|
newFlairGroupId: user.get("flair_group_id"),
|
||||||
|
newStatus: user.status,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,24 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.canSelectUserStatus}}
|
||||||
|
<div class="control-group pref-user-status">
|
||||||
|
<label class="control-label">{{i18n "user.status.title"}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
{{#if this.newStatus}}
|
||||||
|
<UserStatusMessage @status={{this.newStatus}} @showDescription={{true}} />
|
||||||
|
{{else}}
|
||||||
|
<span class="static">{{i18n "user.status.not_set"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
<DButton
|
||||||
|
@action={{action "showUserStatusModal"}}
|
||||||
|
@actionParam={{this.newStatus}}
|
||||||
|
@class="btn-default btn-small pad-left"
|
||||||
|
@icon="pencil-alt"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.canSelectPrimaryGroup}}
|
{{#if this.canSelectPrimaryGroup}}
|
||||||
<div class="control-group pref-primary-group">
|
<div class="control-group pref-primary-group">
|
||||||
<label class="control-label">{{i18n "user.primary_group.title"}}</label>
|
<label class="control-label">{{i18n "user.primary_group.title"}}</label>
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
import {
|
||||||
|
acceptance,
|
||||||
|
exists,
|
||||||
|
query,
|
||||||
|
updateCurrentUser,
|
||||||
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||||
|
import { test } from "qunit";
|
||||||
|
|
||||||
|
async function openUserStatusModal() {
|
||||||
|
await click(".pref-user-status .btn-default");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickEmoji(emoji) {
|
||||||
|
await click(".btn-emoji");
|
||||||
|
await fillIn(".emoji-picker-content .filter", emoji);
|
||||||
|
await click(".results .emoji");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setStatus(status) {
|
||||||
|
await openUserStatusModal();
|
||||||
|
await pickEmoji(status.emoji);
|
||||||
|
await fillIn(".user-status-description", status.description);
|
||||||
|
await click(".modal-footer .btn-primary"); // save and close modal
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance("User Profile - Account - User Status", function (needs) {
|
||||||
|
const username = "eviltrout";
|
||||||
|
const status = {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
};
|
||||||
|
|
||||||
|
needs.user({ username, status });
|
||||||
|
|
||||||
|
test("doesn't render status block if status is disabled in site settings", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = false;
|
||||||
|
await visit(`/u/${username}/preferences/account`);
|
||||||
|
assert.notOk(exists(".pref-user-status"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders status block if status is enabled in site settings", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
await visit(`/u/${username}/preferences/account`);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".pref-user-status .user-status-message"),
|
||||||
|
"status is shown"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(`.pref-user-status .emoji[alt='${status.emoji}']`),
|
||||||
|
"status emoji is correct"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
query(
|
||||||
|
`.pref-user-status .user-status-message-description`
|
||||||
|
).innerText.trim(),
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the status modal sets status", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
updateCurrentUser({ status: null });
|
||||||
|
|
||||||
|
await visit(`/u/${username}/preferences/account`);
|
||||||
|
assert.notOk(
|
||||||
|
exists(".pref-user-status .user-status-message"),
|
||||||
|
"status isn't shown"
|
||||||
|
);
|
||||||
|
|
||||||
|
await setStatus(status);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".pref-user-status .user-status-message"),
|
||||||
|
"status is shown"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(`.pref-user-status .emoji[alt='${status.emoji}']`),
|
||||||
|
"status emoji is correct"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
query(
|
||||||
|
`.pref-user-status .user-status-message-description`
|
||||||
|
).innerText.trim(),
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the status modal updates status", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
await visit(`/u/${username}/preferences/account`);
|
||||||
|
const newStatus = { emoji: "surfing_man", description: "surfing" };
|
||||||
|
await setStatus(newStatus);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".pref-user-status .user-status-message"),
|
||||||
|
"status is shown"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(`.pref-user-status .emoji[alt='${newStatus.emoji}']`),
|
||||||
|
"status emoji is correct"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
query(
|
||||||
|
`.pref-user-status .user-status-message-description`
|
||||||
|
).innerText.trim(),
|
||||||
|
newStatus.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the status modal clears status", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
await visit(`/u/${username}/preferences/account`);
|
||||||
|
await openUserStatusModal();
|
||||||
|
await click(".btn.delete-status");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists(".pref-user-status .user-status-message"),
|
||||||
|
"status isn't shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1965,6 +1965,10 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if SiteSetting.enable_user_status
|
||||||
|
permitted << { status: [:emoji, :description, :ends_at] }
|
||||||
|
end
|
||||||
|
|
||||||
result = params
|
result = params
|
||||||
.permit(permitted, theme_ids: [], seen_popups: [])
|
.permit(permitted, theme_ids: [], seen_popups: [])
|
||||||
.reverse_merge(
|
.reverse_merge(
|
||||||
|
|
|
@ -218,9 +218,17 @@ class UserUpdater
|
||||||
update_sidebar_tag_section_links(attributes[:sidebar_tag_names])
|
update_sidebar_tag_section_links(attributes[:sidebar_tag_names])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if SiteSetting.enable_user_status?
|
||||||
|
update_user_status(attributes[:status])
|
||||||
|
end
|
||||||
|
|
||||||
name_changed = user.name_changed?
|
name_changed = user.name_changed?
|
||||||
if (saved = (!save_options || user.user_option.save) && (user_notification_schedule.nil? || user_notification_schedule.save) && user_profile.save && user.save) &&
|
saved = (!save_options || user.user_option.save) &&
|
||||||
(name_changed && old_user_name.casecmp(attributes.fetch(:name)) != 0)
|
(user_notification_schedule.nil? || user_notification_schedule.save) &&
|
||||||
|
user_profile.save &&
|
||||||
|
user.save
|
||||||
|
|
||||||
|
if saved && (name_changed && old_user_name.casecmp(attributes.fetch(:name)) != 0)
|
||||||
|
|
||||||
StaffActionLogger.new(@actor).log_name_change(
|
StaffActionLogger.new(@actor).log_name_change(
|
||||||
user.id,
|
user.id,
|
||||||
|
@ -341,6 +349,14 @@ class UserUpdater
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_user_status(status)
|
||||||
|
if status.blank?
|
||||||
|
@user.clear_status!
|
||||||
|
else
|
||||||
|
@user.set_status!(status[:description], status[:emoji], status[:ends_at])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
attr_reader :user, :guardian
|
attr_reader :user, :guardian
|
||||||
|
|
||||||
def format_url(website)
|
def format_url(website)
|
||||||
|
|
|
@ -1793,6 +1793,9 @@ en:
|
||||||
title: "Flair"
|
title: "Flair"
|
||||||
none: "(none)"
|
none: "(none)"
|
||||||
instructions: "icon displayed next to your profile picture"
|
instructions: "icon displayed next to your profile picture"
|
||||||
|
status:
|
||||||
|
title: "Custom Status"
|
||||||
|
not_set: "Not set"
|
||||||
primary_group:
|
primary_group:
|
||||||
title: "Primary Group"
|
title: "Primary Group"
|
||||||
none: "(none)"
|
none: "(none)"
|
||||||
|
|
|
@ -2472,6 +2472,224 @@ RSpec.describe UsersController do
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with user status" do
|
||||||
|
context "as a regular user" do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets user status" do
|
||||||
|
status = {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
expect(user.user_status.emoji).to eq(status[:emoji])
|
||||||
|
expect(user.user_status.description).to eq(status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates user status" do
|
||||||
|
user.set_status!("off to dentist", "tooth")
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
new_status = {
|
||||||
|
emoji: "surfing_man",
|
||||||
|
description: "surfing",
|
||||||
|
}
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: new_status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
expect(user.user_status.emoji).to eq(new_status[:emoji])
|
||||||
|
expect(user.user_status.description).to eq(new_status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "clears user status" do
|
||||||
|
user.set_status!("off to dentist", "tooth")
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: nil
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can't set status of another user" do
|
||||||
|
put "/u/#{user1.username}.json", params: {
|
||||||
|
status: {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
|
||||||
|
user1.reload
|
||||||
|
expect(user1.user_status).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can't update status of another user" do
|
||||||
|
old_status = {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
user1.set_status!(old_status[:description], old_status[:emoji])
|
||||||
|
user1.reload
|
||||||
|
|
||||||
|
new_status = {
|
||||||
|
emoji: "surfing_man",
|
||||||
|
description: "surfing",
|
||||||
|
}
|
||||||
|
put "/u/#{user1.username}.json", params: {
|
||||||
|
status: new_status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
|
||||||
|
user1.reload
|
||||||
|
expect(user1.user_status).not_to be_nil
|
||||||
|
expect(user1.user_status.emoji).to eq(old_status[:emoji])
|
||||||
|
expect(user1.user_status.description).to eq(old_status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can't clear status of another user" do
|
||||||
|
user1.set_status!("off to dentist", "tooth")
|
||||||
|
user1.reload
|
||||||
|
|
||||||
|
put "/u/#{user1.username}.json", params: {
|
||||||
|
status: nil
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
|
||||||
|
user1.reload
|
||||||
|
expect(user1.user_status).not_to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user status is disabled' do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_user_status = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't set user status" do
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't update user status" do
|
||||||
|
old_status = {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
user.set_status!(old_status[:description], old_status[:emoji])
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
new_status = {
|
||||||
|
emoji: "surfing_man",
|
||||||
|
description: "surfing",
|
||||||
|
}
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: new_status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
expect(user.user_status.emoji).to eq(old_status[:emoji])
|
||||||
|
expect(user.user_status.description).to eq(old_status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't clear user status" do
|
||||||
|
user.set_status!("off to dentist", "tooth")
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: nil
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as a staff user' do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
sign_in(moderator)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets another user's status" do
|
||||||
|
status = {
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
}
|
||||||
|
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
expect(user.user_status.emoji).to eq(status[:emoji])
|
||||||
|
expect(user.user_status.description).to eq(status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates another user's status" do
|
||||||
|
user.set_status!("off to dentist", "tooth")
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
new_status = {
|
||||||
|
emoji: "surfing_man",
|
||||||
|
description: "surfing",
|
||||||
|
}
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: new_status
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).not_to be_nil
|
||||||
|
expect(user.user_status.emoji).to eq(new_status[:emoji])
|
||||||
|
expect(user.user_status.description).to eq(new_status[:description])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "clears another user's status" do
|
||||||
|
user.set_status!("off to dentist", "tooth")
|
||||||
|
user.reload
|
||||||
|
|
||||||
|
put "/u/#{user.username}.json", params: {
|
||||||
|
status: nil
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
user.reload
|
||||||
|
expect(user.user_status).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#badge_title' do
|
describe '#badge_title' do
|
||||||
|
|
Loading…
Reference in New Issue