FEATURE: show user status on group pages (#19323)

This adds live user status to /g/{group-name} routes.
This commit is contained in:
Andrei Prigorshnev 2022-12-14 13:18:09 +04:00 committed by GitHub
parent 492f68c462
commit ff5a0bec89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 7 deletions

View File

@ -12,6 +12,16 @@ export default Component.extend({
includeLink: true,
includeAvatar: true,
didInsertElement() {
this._super(...arguments);
this.user?.trackStatus?.();
},
willDestroyElement() {
this._super(...arguments);
this.user?.stopTrackingStatus?.();
},
@discourseComputed("user.username")
userPath(username) {
return userPath(username);

View File

@ -28,7 +28,10 @@
{{/if}}
</span>
{{#if (and @showStatus @user.status)}}
<UserStatusMessage @status={{@user.status}} @showDescription={{true}} @showTooltip={{false}} />
<UserStatusMessage
@status={{@user.status}}
@showDescription={{@showStatusDescription}}
@showTooltip={{@showStatusTooltip}} />
{{/if}}
<PluginOutlet @name="after-user-name" @tagName="span" @connectorTagName="span" @args={{hash user=this.user}} />
</div>

View File

@ -54,7 +54,11 @@
{{/if}}
<td class="avatar" colspan="2">
<UserInfo @user={{m}} @skipName={{this.skipName}} />
<UserInfo
@user={{m}}
@skipName={{this.skipName}}
@showStatus={{true}}
@showStatusTooltip={{true}}/>
</td>
<td class="group-owner">

View File

@ -1,6 +1,6 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { render } from "@ember/test-helpers";
import { render, triggerEvent } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
@ -91,4 +91,58 @@ module("Integration | Component | user-info", function (hooks) {
assert.notOk(exists(".user-status-message"));
});
test("doesn't show status description by default", async function (assert) {
this.currentUser.name = "Evil Trout";
this.currentUser.status = { emoji: "tooth", description: "off to dentist" };
await render(
hbs`<UserInfo @user={{this.currentUser}} @showStatus={{true}} />`
);
assert
.dom(".user-status-message .user-status-message-description")
.doesNotExist();
});
test("shows status description if enabled", async function (assert) {
this.currentUser.name = "Evil Trout";
this.currentUser.status = { emoji: "tooth", description: "off to dentist" };
await render(
hbs`<UserInfo @user={{this.currentUser}} @showStatus={{true}} @showStatusDescription={{true}} />`
);
assert
.dom(".user-status-message .user-status-message-description")
.exists();
});
test("doesn't show status tooltip by default", async function (assert) {
this.currentUser.name = "Evil Trout";
this.currentUser.status = { emoji: "tooth", description: "off to dentist" };
await render(
hbs`<UserInfo @user={{this.currentUser}} @showStatus={{true}} />`
);
await triggerEvent(query(".user-status-message"), "mouseenter");
assert.notOk(
document.querySelector("[data-tippy-root] .user-status-message-tooltip")
);
});
test("shows status tooltip if enabled", async function (assert) {
this.currentUser.name = "Evil Trout";
this.currentUser.status = { emoji: "tooth", description: "off to dentist" };
await render(
hbs`<UserInfo @user={{this.currentUser}} @showStatus={{true}} @showStatusTooltip={{true}} />`
);
await triggerEvent(query(".user-status-message"), "mouseenter");
assert.ok(
document.querySelector("[data-tippy-root] .user-status-message-tooltip")
);
});
});

View File

@ -162,6 +162,13 @@ table.group-members {
.avatar-flair {
color: var(--primary);
}
.user-status-message {
img.emoji {
width: 1em;
height: 1em;
}
}
}
}

View File

@ -301,8 +301,8 @@ class GroupsController < ApplicationController
users = users
.includes(:primary_group)
.joins(:user_option)
.select('users.*, user_options.timezone, group_users.created_at as added_at')
.includes(:user_option)
.select('users.*, group_users.created_at as added_at')
.order(order)
.order(username_lower: dir)

View File

@ -8,10 +8,22 @@ class GroupUserSerializer < BasicUserSerializer
:last_posted_at,
:last_seen_at,
:added_at,
:timezone
:timezone,
:status
def timezone
user.user_option.timezone
end
def include_added_at?
object.respond_to? :added_at
end
def include_status?
SiteSetting.enable_user_status && user.has_status?
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
end

View File

@ -48,7 +48,12 @@
{{on "keyup" (action "handleUserKeyUp" user)}}
>
<ChatUserAvatar @user={{user}} @avatarSize="medium" />
<UserInfo @user={{user}} @includeLink={{false}} @includeAvatar={{false}} @showStatus={{true}} />
<UserInfo
@user={{user}}
@includeLink={{false}}
@includeAvatar={{false}}
@showStatus={{true}}
@showStatusDescription={{true}} />
</li>
{{/each}}
</ul>

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
RSpec.describe GroupUserSerializer do
let(:serializer) { described_class.new(user, scope: Guardian.new(user), root: false) }
describe '#status' do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:user) { Fabricate(:user, user_status: user_status) }
it "adds user status when enabled in site settings" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:status]).to_not be_nil do |status|
expect(status.description).to eq(user_status.description)
expect(status.emoji).to eq(user_status.emoji)
end
end
it "doesn't add user status when disabled in site settings" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json.keys).not_to include :status
end
it "doesn't add expired user status" do
SiteSetting.enable_user_status = true
user.user_status.ends_at = 1.minutes.ago
serializer = described_class.new(user, scope: Guardian.new(user), root: false)
json = serializer.as_json
expect(json.keys).not_to include :status
end
it "doesn't return status if user doesn't have it" do
SiteSetting.enable_user_status = true
user.clear_status!
user.reload
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end