From 134da749f9235a53942311d6e872ab15c184c3c1 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 11 Apr 2022 11:30:08 +0200 Subject: [PATCH 1/4] Expose an attribute "self" in the reaction summary with the list of own reactions Signed-off-by: Joas Schilling --- lib/Chat/CommentsManager.php | 28 +++++++++ lib/Chat/ReactionManager.php | 14 +++++ lib/Controller/ChatController.php | 62 +++++++++++++------ .../features/bootstrap/FeatureContext.php | 2 +- .../features/reaction/react.feature | 4 +- tests/php/Controller/ChatControllerTest.php | 5 ++ 6 files changed, 94 insertions(+), 21 deletions(-) diff --git a/lib/Chat/CommentsManager.php b/lib/Chat/CommentsManager.php index cf91bd66222..0e3cf3d67a1 100644 --- a/lib/Chat/CommentsManager.php +++ b/lib/Chat/CommentsManager.php @@ -64,4 +64,32 @@ public function getCommentsById(array $ids): array { return $comments; } + + /** + * @param string $actorType + * @param string $actorId + * @param string[] $messageIds + * @return array + * @psalm-return array + */ + public function retrieveReactionsByActor(string $actorType, string $actorId, array $messageIds): array { + $commentIds = array_map('intval', $messageIds); + + $query = $this->dbConn->getQueryBuilder(); + $query->select('*') + ->from('reactions') + ->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType))) + ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId))) + ->andWhere($query->expr()->in('parent_id', $query->createNamedParameter($commentIds, IQueryBuilder::PARAM_INT_ARRAY))); + + $reactions = []; + $result = $query->executeQuery(); + while ($row = $result->fetch()) { + $reactions[(int) $row['parent_id']] ??= []; + $reactions[(int) $row['parent_id']][] = $row['reaction']; + } + $result->closeCursor(); + + return $reactions; + } } diff --git a/lib/Chat/ReactionManager.php b/lib/Chat/ReactionManager.php index 3e7997ccd47..bf33b259f90 100644 --- a/lib/Chat/ReactionManager.php +++ b/lib/Chat/ReactionManager.php @@ -169,6 +169,20 @@ public function retrieveReactionMessages(Room $chat, Participant $participant, i return $reactions; } + /** + * @param Participant $participant + * @param array $messageIds + * @return array[] + * @psalm-return array + */ + public function getReactionsForMessages(Participant $participant, array $messageIds): array { + return $this->commentsManager->retrieveReactionsByActor( + $participant->getAttendee()->getActorType(), + $participant->getAttendee()->getActorId(), + $messageIds + ); + } + /** * @param Room $chat * @param string $messageId diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 2df304c598c..5c53d635a70 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -28,6 +28,7 @@ use OCA\Talk\Chat\AutoComplete\Sorter; use OCA\Talk\Chat\ChatManager; use OCA\Talk\Chat\MessageParser; +use OCA\Talk\Chat\ReactionManager; use OCA\Talk\GuestManager; use OCA\Talk\MatterbridgeManager; use OCA\Talk\Model\Attachment; @@ -62,44 +63,26 @@ class ChatController extends AEnvironmentAwareController { private ?string $userId; - private IUserManager $userManager; - private IAppManager $appManager; - private ChatManager $chatManager; - + private ReactionManager $reactionManager; private ParticipantService $participantService; - private SessionService $sessionService; - protected AttachmentService $attachmentService; - private GuestManager $guestManager; - /** @var string[] */ protected array $guestNames; - private MessageParser $messageParser; - private IManager $autoCompleteManager; - private IUserStatusManager $statusManager; - protected MatterbridgeManager $matterbridgeManager; - private SearchPlugin $searchPlugin; - private ISearchResult $searchResult; - protected ITimeFactory $timeFactory; - protected IEventDispatcher $eventDispatcher; - protected IValidator $richObjectValidator; - protected ITrustedDomainHelper $trustedDomainHelper; - private IL10N $l; public function __construct(string $appName, @@ -108,6 +91,7 @@ public function __construct(string $appName, IUserManager $userManager, IAppManager $appManager, ChatManager $chatManager, + ReactionManager $reactionManager, ParticipantService $participantService, SessionService $sessionService, AttachmentService $attachmentService, @@ -129,6 +113,7 @@ public function __construct(string $appName, $this->userManager = $userManager; $this->appManager = $appManager; $this->chatManager = $chatManager; + $this->reactionManager = $reactionManager; $this->participantService = $participantService; $this->sessionService = $sessionService; $this->attachmentService = $attachmentService; @@ -516,6 +501,45 @@ public function receiveMessages(int $lookIntoFuture, ]; } + /** + * Gather information to expose $message['reactions']['self'] + */ + $messageIdsWithReactions = array_map( + static fn (array $message) => $message['id'], + array_filter($messages, static fn (array $message) => !empty($message['reactions'])) + ); + + $parentsWithReactions = array_map( + static fn (array $message) => ['parent' => $message['parent']['id'], 'message' => $message['id']], + array_filter($messages, static fn (array $message) => !empty($message['parent']['reactions'])) + ); + + $parentMap = $parentIdsWithReactions = []; + foreach ($parentsWithReactions as $entry) { + // Create a map, so we can translate the parent's $messageId to the correct child entries + $parentMap[(int) $entry['parent']] ??= []; + $parentMap[(int) $entry['parent']][] = (int) $entry['message']; + $parentIdsWithReactions[] = (int) $entry['parent']; + } + + $idsWithReactions = array_unique(array_merge($messageIdsWithReactions, $parentIdsWithReactions)); + + $reactionsById = $this->reactionManager->getReactionsForMessages($this->participant, $idsWithReactions); + foreach ($reactionsById as $messageId => $reactions) { + if (isset($messages[$commentIdToIndex[$messageId]])) { + $messages[$commentIdToIndex[$messageId]]['reactions']['self'] = $reactions; + } + + // Add the self part also to potential parent elements + if (isset($parentMap[$messageId])) { + foreach ($parentMap[$messageId] as $mid) { + if (isset($messages[$commentIdToIndex[$mid]])) { + $messages[$commentIdToIndex[$mid]]['parent']['reactions']['self'] = $reactions; + } + } + } + } + $response = new DataResponse($messages, Http::STATUS_OK); $newLastKnown = end($comments); diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 3d0d3dc7bd0..da36775f01d 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -2267,7 +2267,7 @@ public function userReactWithOnMessageToRoomWith(string $user, string $action, s /** * @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/ */ - public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData): void { + public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void { $token = self::$identifierToToken[$identifier]; $messageId = self::$textToMessageId[$message]; $this->setCurrentUser($user); diff --git a/tests/integration/features/reaction/react.feature b/tests/integration/features/reaction/react.feature index f7c796bf103..10ec2901f1c 100644 --- a/tests/integration/features/reaction/react.feature +++ b/tests/integration/features/reaction/react.feature @@ -24,12 +24,14 @@ Feature: reaction/react | actorType | actorId | actorDisplayName | reaction | | users | participant1 | participant1-displayname | 👍 | | users | participant2 | participant2-displayname | 👍 | + And user "participant1" react with "🚀" on message "Message 1" to room "room" with 201 Then user "participant1" sees the following messages in room "room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions | - | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":2} | + | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":2,"🚀":1,"self":["👍","🚀"]} | Then user "participant1" sees the following system messages in room "room" with 200 | room | actorType | actorId | actorDisplayName | systemMessage | | room | users | participant1 | participant1-displayname | reaction | + | room | users | participant1 | participant1-displayname | reaction | | room | users | participant2 | participant2-displayname | reaction | | room | users | participant1 | participant1-displayname | user_added | | room | users | participant1 | participant1-displayname | conversation_created | diff --git a/tests/php/Controller/ChatControllerTest.php b/tests/php/Controller/ChatControllerTest.php index 6ea619f1d1d..9a79d091612 100644 --- a/tests/php/Controller/ChatControllerTest.php +++ b/tests/php/Controller/ChatControllerTest.php @@ -26,6 +26,7 @@ use OCA\Talk\Chat\AutoComplete\SearchPlugin; use OCA\Talk\Chat\ChatManager; use OCA\Talk\Chat\MessageParser; +use OCA\Talk\Chat\ReactionManager; use OCA\Talk\Controller\ChatController; use OCA\Talk\GuestManager; use OCA\Talk\MatterbridgeManager; @@ -63,6 +64,8 @@ class ChatControllerTest extends TestCase { private $appManager; /** @var ChatManager|MockObject */ protected $chatManager; + /** @var ReactionManager|MockObject */ + protected $reactionManager; /** @var ParticipantService|MockObject */ protected $participantService; /** @var SessionService|MockObject */ @@ -109,6 +112,7 @@ public function setUp(): void { $this->userManager = $this->createMock(IUserManager::class); $this->appManager = $this->createMock(IAppManager::class); $this->chatManager = $this->createMock(ChatManager::class); + $this->reactionManager = $this->createMock(ReactionManager::class); $this->participantService = $this->createMock(ParticipantService::class); $this->sessionService = $this->createMock(SessionService::class); $this->attachmentService = $this->createMock(AttachmentService::class); @@ -145,6 +149,7 @@ private function recreateChatController() { $this->userManager, $this->appManager, $this->chatManager, + $this->reactionManager, $this->participantService, $this->sessionService, $this->attachmentService, From 11fe3c1ce6d983d3aa7d6ce9ca9c015626f5b243 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 11 Apr 2022 12:13:24 +0200 Subject: [PATCH 2/4] Show directly when the user reacted with a reaction already Signed-off-by: Joas Schilling --- .../MessagesGroup/Message/Message.spec.js | 14 +++++++++- .../MessagesGroup/Message/Message.vue | 26 ++++++++++++++----- src/store/messagesStore.js | 13 ++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js index 0bf66164294..49c5752c730 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js +++ b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js @@ -816,6 +816,12 @@ describe('Message.vue', () => { store = new Store(testStoreConfig) + const messagePropsWithReactions = Object.assign({}, messageProps) + messagePropsWithReactions.reactions = { + '👍': 1, + self: ['👍'], + } + const wrapper = shallowMount(Message, { localVue, store, @@ -848,10 +854,16 @@ describe('Message.vue', () => { store = new Store(testStoreConfig) + const messagePropsWithReactions = Object.assign({}, messageProps) + messagePropsWithReactions.reactions = { + '❤️': 1, + self: ['❤️'], + } + const wrapper = shallowMount(Message, { localVue, store, - propsData: messageProps, + propsData: messagePropsWithReactions, }) // Click reaction button upon having already reacted diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 28181b35596..39ee135c5f2 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -126,6 +126,7 @@ the main body of the message as well as a quote.