mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
feat(pinned): Expose pin actor and pinned until for messages
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
bf0f5f05e4
commit
3e32fbb0dd
7 changed files with 140 additions and 43 deletions
|
|
@ -44,7 +44,10 @@ class MessageParser {
|
|||
}
|
||||
|
||||
public function createMessage(Room $room, ?Participant $participant, IComment $comment, IL10N $l): Message {
|
||||
return new Message($room, $participant, $comment, $l);
|
||||
$message = new Message($room, $participant, $comment, $l);
|
||||
$metaData = $this->addPinnedActorDisplayNameInfo($message, $comment->getMetaData() ?? []);
|
||||
$message->setMetaData($metaData);
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function createMessageFromProxyCache(Room $room, ?Participant $participant, ProxyCacheMessage $proxy, IL10N $l): Message {
|
||||
|
|
@ -63,6 +66,15 @@ class MessageParser {
|
|||
$proxy->getParsedMessageParameters()
|
||||
);
|
||||
|
||||
try {
|
||||
$metaData = json_decode($proxy->getMetaData(), true, flags: JSON_THROW_ON_ERROR);
|
||||
if (is_array($metaData)) {
|
||||
$metaData = $this->addPinnedActorDisplayNameInfo($message, $metaData);
|
||||
$message->setMetaData($metaData);
|
||||
}
|
||||
} catch (\JsonException) {
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +133,21 @@ class MessageParser {
|
|||
}
|
||||
}
|
||||
|
||||
protected function addPinnedActorDisplayNameInfo(Message $message, array $metaData): array {
|
||||
if (isset($metaData[Message::METADATA_PINNED_BY_TYPE], $metaData[Message::METADATA_PINNED_BY_ID])) {
|
||||
[$actorType, $actorId, $displayName] = $this->getActorInformation(
|
||||
$message,
|
||||
$metaData[Message::METADATA_PINNED_BY_TYPE],
|
||||
$metaData[Message::METADATA_PINNED_BY_ID],
|
||||
);
|
||||
|
||||
$metaData[Message::METADATA_PINNED_BY_TYPE] = $actorType;
|
||||
$metaData[Message::METADATA_PINNED_BY_ID] = $actorId;
|
||||
$metaData[Message::METADATA_PINNED_BY_NAME] = $displayName;
|
||||
}
|
||||
return $metaData;
|
||||
}
|
||||
|
||||
protected function getActorInformation(Message $message, string $actorType, string $actorId, string $displayName = ''): array {
|
||||
if ($actorType === Attendee::ACTOR_USERS) {
|
||||
$tempDisplayName = $this->userManager->getDisplayName($actorId);
|
||||
|
|
|
|||
|
|
@ -463,7 +463,21 @@ class CloudFederationProviderTalk implements ICloudFederationProvider, ISignedCl
|
|||
// Note: `messageParameters` (array during parsing) vs `messageParameter` (string during sending)
|
||||
$notification['messageData']['messageParameters'] = json_decode($notification['messageData']['messageParameter'], true, flags: JSON_THROW_ON_ERROR);
|
||||
unset($notification['messageData']['messageParameter']);
|
||||
|
||||
if (isset($notification['messageData']['metaData'])) {
|
||||
// Decode metaData to array and after converting back to string
|
||||
$notification['messageData']['metaData'] = json_decode($notification['messageData']['metaData'], true, flags: JSON_THROW_ON_ERROR);
|
||||
} else {
|
||||
$notification['messageData']['metaData'] = [];
|
||||
}
|
||||
|
||||
$converted = $this->userConverter->convertMessage($room, $notification['messageData']);
|
||||
|
||||
if (!empty($converted['metaData'])) {
|
||||
$converted['metaData'] = json_encode($converted['metaData'], JSON_THROW_ON_ERROR);
|
||||
} else {
|
||||
unset($converted['metaData']);
|
||||
}
|
||||
$converted['messageParameter'] = json_encode($converted['messageParameters'], JSON_THROW_ON_ERROR);
|
||||
unset($converted['messageParameters']);
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@ class UserConverter {
|
|||
$message['token'] = $room->getToken();
|
||||
$message = $this->convertAttendee($room, $message, 'actorType', 'actorId', 'actorDisplayName');
|
||||
$message = $this->convertAttendee($room, $message, 'lastEditActorType', 'lastEditActorId', 'lastEditActorDisplayName');
|
||||
if (!empty($message['metaData']['pinnedActorType'])) {
|
||||
$message['metaData'] = $this->convertAttendee($room, $message['metaData'], 'pinnedActorType', 'pinnedActorId', 'pinnedActorDisplayName');
|
||||
}
|
||||
$message = $this->convertMessageParameters($room, $message);
|
||||
|
||||
if (isset($message['parent'])) {
|
||||
|
|
|
|||
|
|
@ -27,44 +27,29 @@ class Message {
|
|||
public const METADATA_THREAD_ID = 'thread_id';
|
||||
public const METADATA_PINNED_BY_TYPE = 'pinned_by_type';
|
||||
public const METADATA_PINNED_BY_ID = 'pinned_by_id';
|
||||
public const METADATA_PINNED_BY_NAME = 'pinned_by_name';
|
||||
public const METADATA_PINNED_MESSAGE_ID = 'pinned_id';
|
||||
public const METADATA_PINNED_UNTIL = 'pinned_until';
|
||||
public const EXPOSED_METADATA_KEYS = [
|
||||
self::METADATA_PINNED_BY_TYPE => 'pinnedActorType',
|
||||
self::METADATA_PINNED_BY_ID => 'pinnedActorId',
|
||||
self::METADATA_PINNED_BY_NAME => 'pinnedActorName',
|
||||
self::METADATA_PINNED_UNTIL => 'pinnedUntil',
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
protected $visible = true;
|
||||
|
||||
/** @var string */
|
||||
protected $type = '';
|
||||
|
||||
/** @var string */
|
||||
protected $message = '';
|
||||
|
||||
/** @var string */
|
||||
protected $rawMessage = '';
|
||||
|
||||
/** @var array */
|
||||
protected $parameters = [];
|
||||
|
||||
/** @var string */
|
||||
protected $actorType = '';
|
||||
|
||||
/** @var string */
|
||||
protected $actorId = '';
|
||||
|
||||
/** @var string */
|
||||
protected $actorDisplayName = '';
|
||||
|
||||
/** @var string */
|
||||
protected $lastEditActorType = '';
|
||||
|
||||
/** @var string */
|
||||
protected $lastEditActorId = '';
|
||||
|
||||
/** @var string */
|
||||
protected $lastEditActorDisplayName = '';
|
||||
|
||||
/** @var int */
|
||||
protected $lastEditTimestamp = 0;
|
||||
protected bool $visible = true;
|
||||
protected string $type = '';
|
||||
protected string $message = '';
|
||||
protected string $rawMessage = '';
|
||||
protected array $parameters = [];
|
||||
protected string $actorType = '';
|
||||
protected string $actorId = '';
|
||||
protected string $actorDisplayName = '';
|
||||
protected string $lastEditActorType = '';
|
||||
protected string $lastEditActorId = '';
|
||||
protected string $lastEditActorDisplayName = '';
|
||||
protected int $lastEditTimestamp = 0;
|
||||
protected array $metaData = [];
|
||||
|
||||
public function __construct(
|
||||
protected Room $room,
|
||||
|
|
@ -154,6 +139,10 @@ class Message {
|
|||
$this->lastEditTimestamp = $timestamp;
|
||||
}
|
||||
|
||||
public function setMetaData(array $metaData): void {
|
||||
$this->metaData = $metaData;
|
||||
}
|
||||
|
||||
public function getActorType(): string {
|
||||
return $this->actorType;
|
||||
}
|
||||
|
|
@ -242,6 +231,17 @@ class Message {
|
|||
$data[self::METADATA_SILENT] = true;
|
||||
}
|
||||
|
||||
$data['metaData'] = [];
|
||||
foreach (self::EXPOSED_METADATA_KEYS as $exposedKey => $exposedAs) {
|
||||
if (isset($this->metaData[$exposedKey])) {
|
||||
$data['metaData'][$exposedAs] = $this->metaData[$exposedKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($data['metaData'])) {
|
||||
unset($data['metaData']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ namespace OCA\Talk;
|
|||
* isThread?: bool,
|
||||
* threadTitle?: string,
|
||||
* threadReplies?: int,
|
||||
* metaData?: array{
|
||||
* pinnedActorType?: string,
|
||||
* pinnedActorId?: string,
|
||||
* pinnedActorDisplayName?: string,
|
||||
* pinnedUntil?: int,
|
||||
* },
|
||||
* }
|
||||
*
|
||||
* @psalm-type TalkChatProxyMessage = TalkBaseMessage
|
||||
|
|
|
|||
|
|
@ -2051,13 +2051,24 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
#[Then('/^user "([^"]*)" (unpins|pins|hides pinned) message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
|
||||
public function userPinsMessage(string $user, string $action, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
|
||||
public function userPinActionMessage(string $user, string $action, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
|
||||
$this->userPinActionWithTimeMessage($user, $action, $message, 0, $identifier, $statusCode, $apiVersion);
|
||||
}
|
||||
|
||||
#[Then('/^user "([^"]*)" (unpins|pins|hides pinned) message "([^"]*)" for (\d+) seconds in room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
|
||||
public function userPinActionWithTimeMessage(string $user, string $action, string $message, int $duration, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
|
||||
$this->setCurrentUser($user);
|
||||
|
||||
$body = [];
|
||||
if ($action === 'pins' && $duration !== 0) {
|
||||
$body['pinUntil'] = time() + $duration;
|
||||
}
|
||||
|
||||
$routeSuffix = $action === 'hides pinned' ? '/self' : '';
|
||||
$this->sendRequest(
|
||||
$action === 'pins' ? 'POST' : 'DELETE',
|
||||
'/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/' . self::$textToMessageId[$message] . '/pin' . $routeSuffix
|
||||
'/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/' . self::$textToMessageId[$message] . '/pin' . $routeSuffix,
|
||||
$body
|
||||
);
|
||||
|
||||
$this->assertStatusCode($this->response, $statusCode);
|
||||
|
|
@ -2701,6 +2712,13 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
$includeMessageType = in_array('messageType', $formData->getRow(0), true);
|
||||
$includeThreadTitle = in_array('threadTitle', $formData->getRow(0), true);
|
||||
$includeThreadReplies = in_array('threadReplies', $formData->getRow(0), true);
|
||||
$includeMetaDataKeys = array_map(
|
||||
static fn (string $field): string => substr($field, strlen('metaData.')),
|
||||
array_filter(
|
||||
$formData->getRow(0),
|
||||
static fn (string $field): bool => str_starts_with($field, 'metaData.')
|
||||
)
|
||||
);
|
||||
|
||||
$expected = $formData->getHash();
|
||||
$count = count($expected);
|
||||
|
|
@ -2775,7 +2793,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
}
|
||||
|
||||
Assert::assertEquals($expected, array_map(function ($message, $expected) use ($includeParents, $includeReferenceId, $includeReactions, $includeReactionsSelf, $includeLastEdit, $includeMessageType, $includeThreadTitle, $includeThreadReplies) {
|
||||
Assert::assertEquals($expected, array_map(function ($message, $expected) use ($includeParents, $includeReferenceId, $includeReactions, $includeReactionsSelf, $includeLastEdit, $includeMessageType, $includeThreadTitle, $includeThreadReplies, $includeMetaDataKeys) {
|
||||
$data = [
|
||||
'room' => self::$tokenToIdentifier[$message['token']],
|
||||
'actorType' => $message['actorType'],
|
||||
|
|
@ -2826,6 +2844,17 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
$data['threadReplies'] = $message['threadReplies'] ?? null;
|
||||
}
|
||||
|
||||
if (!empty($includeMetaDataKeys)) {
|
||||
$metaData = $message['metaData'] ?? [];
|
||||
foreach ($includeMetaDataKeys as $key) {
|
||||
$data['metaData.' . $key] = $metaData[$key] ?? 'UNSET';
|
||||
$expectedValue = $expected['metaData.' . $key];
|
||||
if ($expectedValue === 'NUMERIC' && is_numeric($data['metaData.' . $key])) {
|
||||
$data['metaData.' . $key] = $expectedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}, $messages, $expected));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ Feature: chat-1/pinned-messages
|
|||
And user "participant1" adds user "participant2" to room "room" with 200 (v4)
|
||||
When user "participant1" sends message "Message 1" to room "room" with 201
|
||||
When user "participant1" sends message "Message 2" to room "room" with 201
|
||||
|
||||
# Pinned messages are sorted by moment of pinning
|
||||
When user "participant2" pins message "Message 2" in room "room" with 403
|
||||
When user "participant1" pins message "Message 2" in room "room" with 200
|
||||
Then user "participant2" is participant of the following rooms (v4)
|
||||
|
|
@ -38,9 +40,11 @@ Feature: chat-1/pinned-messages
|
|||
| room | users | participant1 | user_added | You added {user} | "IGNORE" |
|
||||
| room | users | participant1 | conversation_created | You created the conversation | "IGNORE" |
|
||||
Then user "participant1" sees the following shared pinned in room "room" with 200
|
||||
| room | actorType | actorId | actorDisplayName | message | messageParameters |
|
||||
| room | users | participant1 | participant1-displayname | Message 1 | [] |
|
||||
| room | users | participant1 | participant1-displayname | Message 2 | [] |
|
||||
| room | actorType | actorId | actorDisplayName | message | messageParameters | metaData.pinnedActorDisplayName | metaData.pinnedUntil |
|
||||
| room | users | participant1 | participant1-displayname | Message 1 | [] | participant1-displayname | UNSET |
|
||||
| room | users | participant1 | participant1-displayname | Message 2 | [] | participant1-displayname | UNSET |
|
||||
|
||||
# Unpinning resets lastPinnedId
|
||||
When user "participant1" unpins message "Message 1" in room "room" with 200
|
||||
Then user "participant1" sees the following system messages in room "room" with 200
|
||||
| room | actorType | actorId | systemMessage | message | messageParameters |
|
||||
|
|
@ -55,6 +59,8 @@ Feature: chat-1/pinned-messages
|
|||
Then user "participant2" is participant of the following rooms (v4)
|
||||
| id | type | lastPinnedId | hidePinnedId |
|
||||
| room | 3 | Message 2 | EMPTY |
|
||||
|
||||
# Hide as user
|
||||
When user "participant2" hides pinned message "Message 2" in room "room" with 200
|
||||
Then user "participant2" is participant of the following rooms (v4)
|
||||
| id | type | lastPinnedId | hidePinnedId |
|
||||
|
|
@ -63,7 +69,19 @@ Feature: chat-1/pinned-messages
|
|||
Then user "participant2" is participant of the following rooms (v4)
|
||||
| id | type | lastPinnedId | hidePinnedId |
|
||||
| room | 3 | EMPTY | Message 2 |
|
||||
When user "participant1" pins message "Message 2" in room "room" with 200
|
||||
|
||||
# Pin temporarily
|
||||
When user "participant1" pins message "Message 2" for 3 seconds in room "room" with 200
|
||||
Then user "participant1" sees the following shared pinned in room "room" with 200
|
||||
| room | actorType | actorId | actorDisplayName | message | messageParameters | metaData.pinnedActorDisplayName | metaData.pinnedUntil |
|
||||
| room | users | participant1 | participant1-displayname | Message 2 | [] | participant1-displayname | NUMERIC |
|
||||
Then user "participant2" is participant of the following rooms (v4)
|
||||
| id | type | lastPinnedId | hidePinnedId |
|
||||
| room | 3 | Message 2 | EMPTY |
|
||||
When wait for 4 seconds
|
||||
And run "OCA\Talk\BackgroundJob\UnpinMessage" background jobs
|
||||
Then user "participant1" sees the following shared pinned in room "room" with 200
|
||||
| room | actorType | actorId | actorDisplayName | message | messageParameters | metaData.pinnedActorDisplayName | metaData.pinnedUntil |
|
||||
Then user "participant2" is participant of the following rooms (v4)
|
||||
| id | type | lastPinnedId | hidePinnedId |
|
||||
| room | 3 | EMPTY | EMPTY |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue