From ff5a0bec89678ea34ba5dd310fb9aa18c0050ec8 Mon Sep 17 00:00:00 2001 From: Andrei Prigorshnev Date: Wed, 14 Dec 2022 13:18:09 +0400 Subject: [PATCH] FEATURE: show user status on group pages (#19323) This adds live user status to /g/{group-name} routes. --- .../discourse/app/components/user-info.js | 10 ++++ .../app/templates/components/user-info.hbs | 5 +- .../discourse/app/templates/group-index.hbs | 6 +- .../integration/components/user-info-test.js | 56 ++++++++++++++++++- app/assets/stylesheets/common/base/group.scss | 7 +++ app/controllers/groups_controller.rb | 4 +- app/serializers/group_user_serializer.rb | 14 ++++- .../components/direct-message-creator.hbs | 7 ++- .../serializers/group_user_serializer_spec.rb | 47 ++++++++++++++++ 9 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 spec/serializers/group_user_serializer_spec.rb diff --git a/app/assets/javascripts/discourse/app/components/user-info.js b/app/assets/javascripts/discourse/app/components/user-info.js index 9fdcf782c6a..d211ec1eff3 100644 --- a/app/assets/javascripts/discourse/app/components/user-info.js +++ b/app/assets/javascripts/discourse/app/components/user-info.js @@ -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); diff --git a/app/assets/javascripts/discourse/app/templates/components/user-info.hbs b/app/assets/javascripts/discourse/app/templates/components/user-info.hbs index 98f868a492f..9e32ff4303e 100644 --- a/app/assets/javascripts/discourse/app/templates/components/user-info.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/user-info.hbs @@ -28,7 +28,10 @@ {{/if}} {{#if (and @showStatus @user.status)}} - + {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/group-index.hbs b/app/assets/javascripts/discourse/app/templates/group-index.hbs index 0408dd2e1a1..6f431a1a06f 100644 --- a/app/assets/javascripts/discourse/app/templates/group-index.hbs +++ b/app/assets/javascripts/discourse/app/templates/group-index.hbs @@ -54,7 +54,11 @@ {{/if}} - + diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js index 604481403b9..57092a42b57 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js @@ -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`` + ); + + 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`` + ); + + 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`` + ); + 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`` + ); + await triggerEvent(query(".user-status-message"), "mouseenter"); + + assert.ok( + document.querySelector("[data-tippy-root] .user-status-message-tooltip") + ); + }); }); diff --git a/app/assets/stylesheets/common/base/group.scss b/app/assets/stylesheets/common/base/group.scss index 6bf464c1702..388635e451b 100644 --- a/app/assets/stylesheets/common/base/group.scss +++ b/app/assets/stylesheets/common/base/group.scss @@ -162,6 +162,13 @@ table.group-members { .avatar-flair { color: var(--primary); } + + .user-status-message { + img.emoji { + width: 1em; + height: 1em; + } + } } } diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index d4b856d73ba..830f3ad230f 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -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) diff --git a/app/serializers/group_user_serializer.rb b/app/serializers/group_user_serializer.rb index 0c1d9a43951..fa5bbda7867 100644 --- a/app/serializers/group_user_serializer.rb +++ b/app/serializers/group_user_serializer.rb @@ -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 diff --git a/plugins/chat/assets/javascripts/discourse/components/direct-message-creator.hbs b/plugins/chat/assets/javascripts/discourse/components/direct-message-creator.hbs index 02ae6ecb222..83efb708d08 100644 --- a/plugins/chat/assets/javascripts/discourse/components/direct-message-creator.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/direct-message-creator.hbs @@ -48,7 +48,12 @@ {{on "keyup" (action "handleUserKeyUp" user)}} > - + {{/each}} diff --git a/spec/serializers/group_user_serializer_spec.rb b/spec/serializers/group_user_serializer_spec.rb new file mode 100644 index 00000000000..e9e19c090e0 --- /dev/null +++ b/spec/serializers/group_user_serializer_spec.rb @@ -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