mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
feat(mentions): allow teams to be mentioned
Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
parent
0a6162d315
commit
e193de59f6
16 changed files with 410 additions and 21 deletions
|
|
@ -71,6 +71,8 @@ class SearchPlugin implements ISearchPlugin {
|
|||
$emailAttendees = [];
|
||||
/** @var list<Attendee> $guestAttendees */
|
||||
$guestAttendees = [];
|
||||
/** @var array<string, string> $teamIds */
|
||||
$teamIds = [];
|
||||
|
||||
if ($this->room->getType() === Room::TYPE_ONE_TO_ONE) {
|
||||
// Add potential leavers of one-to-one rooms again.
|
||||
|
|
@ -92,6 +94,8 @@ class SearchPlugin implements ISearchPlugin {
|
|||
$cloudIds[$attendee->getActorId()] = $attendee->getDisplayName();
|
||||
} elseif ($attendee->getActorType() === Attendee::ACTOR_GROUPS) {
|
||||
$groupIds[$attendee->getActorId()] = $attendee->getDisplayName();
|
||||
} elseif ($attendee->getActorType() === Attendee::ACTOR_CIRCLES) {
|
||||
$teamIds[$attendee->getActorId()] = $attendee->getDisplayName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +105,7 @@ class SearchPlugin implements ISearchPlugin {
|
|||
$this->searchGuests($search, $guestAttendees, $searchResult);
|
||||
$this->searchEmails($search, $emailAttendees, $searchResult);
|
||||
$this->searchFederatedUsers($search, $cloudIds, $searchResult);
|
||||
$this->searchTeams($search, $teamIds, $searchResult);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -352,6 +357,58 @@ class SearchPlugin implements ISearchPlugin {
|
|||
$searchResult->addResultSet($type, $matches, $exactMatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $search
|
||||
* @param array<string, Attendee> $attendees
|
||||
* @param ISearchResult $searchResult
|
||||
*/
|
||||
/**
|
||||
* @param array<string|int, string> $teams
|
||||
*/
|
||||
protected function searchTeams(string $search, array $teams, ISearchResult $searchResult): void {
|
||||
$search = strtolower($search);
|
||||
|
||||
$type = new SearchResultType('teams');
|
||||
|
||||
$matches = $exactMatches = [];
|
||||
foreach ($teams as $teamId => $displayName) {
|
||||
if ($displayName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$teamId = (string)$teamId;
|
||||
if ($searchResult->hasResult($type, $teamId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($search === '') {
|
||||
$matches[] = $this->createTeamResult($teamId, $displayName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strtolower($teamId) === $search) {
|
||||
$exactMatches[] = $this->createTeamResult($teamId, $displayName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stripos($teamId, $search) !== false) {
|
||||
$matches[] = $this->createTeamResult($teamId, $displayName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strtolower($displayName) === $search) {
|
||||
$exactMatches[] = $this->createTeamResult($teamId, $displayName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stripos($displayName, $search) !== false) {
|
||||
$matches[] = $this->createTeamResult($teamId, $displayName);
|
||||
}
|
||||
}
|
||||
|
||||
$searchResult->addResultSet($type, $matches, $exactMatches);
|
||||
}
|
||||
|
||||
protected function createResult(string $type, string $uid, string $name): array {
|
||||
if ($type === 'user' && $name === '') {
|
||||
$name = $this->userManager->getDisplayName($uid) ?? $uid;
|
||||
|
|
@ -401,4 +458,14 @@ class SearchPlugin implements ISearchPlugin {
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function createTeamResult(string $actorId, string $name): array {
|
||||
return [
|
||||
'label' => $name,
|
||||
'value' => [
|
||||
'shareType' => 'team',
|
||||
'shareWith' => 'team/' . $actorId,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ class Notifier {
|
|||
public function getUsersToNotify(Room $chat, IComment $comment, array $alreadyNotifiedUsers, ?Participant $participant = null): array {
|
||||
$usersToNotify = $this->getMentionedUsers($comment);
|
||||
$usersToNotify = $this->getMentionedGroupMembers($chat, $comment, $usersToNotify);
|
||||
$usersToNotify = $this->getMentionedTeamMembers($chat, $comment, $usersToNotify);
|
||||
$usersToNotify = $this->addMentionAllToList($chat, $usersToNotify, $participant);
|
||||
$usersToNotify = $this->removeAlreadyNotifiedUsers($usersToNotify, $alreadyNotifiedUsers);
|
||||
|
||||
|
|
@ -532,6 +533,57 @@ class Notifier {
|
|||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Room $chat
|
||||
* @param IComment $comment
|
||||
* @param array $list
|
||||
* @psalm-param array<int, array{type: string, id: string, reason: string, sourceId?: string}> $list
|
||||
* @return array[]
|
||||
* @psalm-return array<int, array{type: string, id: string, reason: string, sourceId?: string}>
|
||||
*/
|
||||
private function getMentionedTeamMembers(Room $chat, IComment $comment, array $list): array {
|
||||
$mentions = $comment->getMentions();
|
||||
|
||||
if (empty($mentions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alreadyMentionedUserIds = array_filter(
|
||||
array_map(static fn (array $entry) => $entry['type'] === Attendee::ACTOR_USERS ? $entry['id'] : null, $list),
|
||||
static fn ($userId) => $userId !== null
|
||||
);
|
||||
$alreadyMentionedUserIds = array_flip($alreadyMentionedUserIds);
|
||||
|
||||
foreach ($mentions as $mention) {
|
||||
if ($mention['type'] !== 'team') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->participantService->getParticipantByActor($chat, Attendee::ACTOR_CIRCLES, $mention['id']);
|
||||
} catch (ParticipantNotFoundException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$members = $this->participantService->getCircleMembers($mention['id']);
|
||||
if (empty($members)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($members as $member) {
|
||||
$list[] = [
|
||||
'id' => $member->getUserId(),
|
||||
'type' => Attendee::ACTOR_USERS,
|
||||
'reason' => 'team',
|
||||
'sourceId' => $mention['id'],
|
||||
];
|
||||
$alreadyMentionedUserIds[$member->getUserId()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notification for the given chat message comment and mentioned
|
||||
* user ID.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Talk\Chat\Parser;
|
||||
|
||||
use OCA\Circles\CirclesManager;
|
||||
use OCA\Talk\Chat\ChatManager;
|
||||
use OCA\Talk\Events\MessageParseEvent;
|
||||
use OCA\Talk\Exceptions\ParticipantNotFoundException;
|
||||
|
|
@ -17,6 +18,7 @@ use OCA\Talk\Model\Message;
|
|||
use OCA\Talk\Room;
|
||||
use OCA\Talk\Service\AvatarService;
|
||||
use OCA\Talk\Service\ParticipantService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
|
|
@ -25,14 +27,20 @@ use OCP\IGroup;
|
|||
use OCP\IGroupManager;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Server;
|
||||
|
||||
/**
|
||||
* Helper class to get a rich message from a plain text message.
|
||||
* @template-implements IEventListener<Event>
|
||||
*/
|
||||
class UserMention implements IEventListener {
|
||||
/** @var array<string, string> */
|
||||
protected array $circleNames = [];
|
||||
/** @var array<string, string> */
|
||||
protected array $circleLinks = [];
|
||||
|
||||
public function __construct(
|
||||
protected IAppManager $appManager,
|
||||
protected ICommentsManager $commentsManager,
|
||||
protected IUserManager $userManager,
|
||||
protected IGroupManager $groupManager,
|
||||
|
|
@ -117,7 +125,7 @@ class UserMention implements IEventListener {
|
|||
$mention['type'] === 'email' ||
|
||||
$mention['type'] === 'group' ||
|
||||
// $mention['type'] === 'federated_group' ||
|
||||
// $mention['type'] === 'team' ||
|
||||
$mention['type'] === 'team' ||
|
||||
// $mention['type'] === 'federated_team' ||
|
||||
$mention['type'] === 'federated_user') {
|
||||
$search = $mention['type'] . '/' . $mention['id'];
|
||||
|
|
@ -135,7 +143,7 @@ class UserMention implements IEventListener {
|
|||
&& !str_starts_with($search, 'email/')
|
||||
&& !str_starts_with($search, 'group/')
|
||||
// && !str_starts_with($search, 'federated_group/')
|
||||
// && !str_starts_with($search, 'team/')
|
||||
&& !str_starts_with($search, 'team/')
|
||||
// && !str_starts_with($search, 'federated_team/')
|
||||
&& !str_starts_with($search, 'federated_user/')) {
|
||||
$message = str_replace('@' . $search, '{' . $mentionParameterId . '}', $message);
|
||||
|
|
@ -213,6 +221,8 @@ class UserMention implements IEventListener {
|
|||
'id' => $mention['id'],
|
||||
'name' => $displayName,
|
||||
];
|
||||
} elseif ($mention['type'] === 'team') {
|
||||
$messageParameters[$mentionParameterId] = $this->getCircle($mention['id']);
|
||||
} else {
|
||||
try {
|
||||
$displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']);
|
||||
|
|
@ -256,4 +266,47 @@ class UserMention implements IEventListener {
|
|||
throw new \InvalidArgumentException('Unknown room type');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCircle(string $circleId): array {
|
||||
if (!$this->appManager->isEnabledForUser('circles')) {
|
||||
return [
|
||||
'type' => 'highlight',
|
||||
'id' => $circleId,
|
||||
'name' => $circleId,
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($this->circleNames[$circleId])) {
|
||||
$this->loadCircleDetails($circleId);
|
||||
}
|
||||
|
||||
if (!isset($this->circleNames[$circleId])) {
|
||||
return [
|
||||
'type' => 'highlight',
|
||||
'id' => $circleId,
|
||||
'name' => $circleId,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'circle',
|
||||
'id' => $circleId,
|
||||
'name' => $this->circleNames[$circleId],
|
||||
'link' => $this->circleLinks[$circleId],
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadCircleDetails(string $circleId): void {
|
||||
try {
|
||||
$circlesManager = Server::get(CirclesManager::class);
|
||||
$circlesManager->startSuperSession();
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
|
||||
$this->circleNames[$circleId] = $circle->getDisplayName();
|
||||
$this->circleLinks[$circleId] = $circle->getUrl();
|
||||
} catch (\Exception) {
|
||||
} finally {
|
||||
$circlesManager?->stopSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1147,7 +1147,7 @@ class RoomController extends AEnvironmentAwareOCSController {
|
|||
* Add a participant to a room
|
||||
*
|
||||
* @param string $newParticipant New participant
|
||||
* @param 'users'|'groups'|'circles'|'emails'|'federated_users'|'phones' $source Source of the participant
|
||||
* @param 'users'|'groups'|'circles'|'emails'|'federated_users'|'phones'|'teams' $source Source of the participant
|
||||
* @return DataResponse<Http::STATUS_OK, array{type?: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_NOT_IMPLEMENTED, array{error: 'ban'|'cloud-id'|'federation'|'moderator'|'new-participant'|'outgoing'|'reach-remote'|'room-type'|'sip'|'source'|'trusted-servers'}, array{}>
|
||||
*
|
||||
* 200: Participant successfully added
|
||||
|
|
@ -1215,7 +1215,7 @@ class RoomController extends AEnvironmentAwareOCSController {
|
|||
}
|
||||
|
||||
$this->participantService->addGroup($this->room, $group, $participants);
|
||||
} elseif ($source === 'circles') {
|
||||
} elseif ($source === 'circles' || $source === 'teams') {
|
||||
if (!$this->appManager->isEnabledForUser('circles')) {
|
||||
return new DataResponse(['error' => 'new-participant'], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Talk\Notification;
|
||||
|
||||
use OCA\Circles\CirclesManager;
|
||||
use OCA\FederatedFileSharing\AddressHandler;
|
||||
use OCA\Talk\AppInfo\Application;
|
||||
use OCA\Talk\Chat\ChatManager;
|
||||
|
|
@ -28,6 +29,7 @@ use OCA\Talk\Room;
|
|||
use OCA\Talk\Service\AvatarService;
|
||||
use OCA\Talk\Service\ParticipantService;
|
||||
use OCA\Talk\Webinary;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
|
|
@ -47,6 +49,7 @@ use OCP\Notification\INotification;
|
|||
use OCP\Notification\INotifier;
|
||||
use OCP\Notification\UnknownNotificationException;
|
||||
use OCP\RichObjectStrings\Definitions;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
use OCP\Share\IShare;
|
||||
|
|
@ -57,12 +60,17 @@ class Notifier implements INotifier {
|
|||
protected array $rooms = [];
|
||||
/** @var Participant[][] */
|
||||
protected array $participants = [];
|
||||
/** @var array<string, string> */
|
||||
protected array $circleNames = [];
|
||||
/** @var array<string, string> */
|
||||
protected array $circleLinks = [];
|
||||
protected ICommentsManager $commentManager;
|
||||
|
||||
public function __construct(
|
||||
protected IFactory $lFactory,
|
||||
protected IURLGenerator $url,
|
||||
protected Config $config,
|
||||
protected IAppManager $appManager,
|
||||
protected IUserManager $userManager,
|
||||
protected IGroupManager $groupManager,
|
||||
protected GuestManager $guestManager,
|
||||
|
|
@ -262,7 +270,7 @@ class Notifier implements INotifier {
|
|||
}
|
||||
return $this->parseCall($notification, $room, $l);
|
||||
}
|
||||
if ($subject === 'reply' || $subject === 'mention' || $subject === 'mention_direct' || $subject === 'mention_group' || $subject === 'mention_all' || $subject === 'chat' || $subject === 'reaction' || $subject === 'reminder') {
|
||||
if ($subject === 'reply' || $subject === 'mention' || $subject === 'mention_direct' || $subject === 'mention_group' || $subject === 'mention_team' || $subject === 'mention_all' || $subject === 'chat' || $subject === 'reaction' || $subject === 'reminder') {
|
||||
if ($participant instanceof Participant &&
|
||||
$room->getLobbyState() !== Webinary::LOBBY_NONE &&
|
||||
!($participant->getPermissions() & Attendee::PERMISSIONS_LOBBY_IGNORE)) {
|
||||
|
|
@ -774,6 +782,9 @@ class Notifier implements INotifier {
|
|||
];
|
||||
|
||||
$subject = $l->t('{user} mentioned group {group} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_team') {
|
||||
$richSubjectParameters['team'] = $this->getCircle($subjectParameters['sourceId']);
|
||||
$subject = $l->t('{user} mentioned team {team} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_all') {
|
||||
$subject = $l->t('{user} mentioned everyone in conversation {call}');
|
||||
} else {
|
||||
|
|
@ -789,6 +800,9 @@ class Notifier implements INotifier {
|
|||
];
|
||||
|
||||
$subject = $l->t('A deleted user mentioned group {group} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_team') {
|
||||
$richSubjectParameters['team'] = $this->getCircle($subjectParameters['sourceId']);
|
||||
$subject = $l->t('A deleted user mentioned team {team} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_all') {
|
||||
$subject = $l->t('A deleted user mentioned everyone in conversation {call}');
|
||||
} else {
|
||||
|
|
@ -806,6 +820,9 @@ class Notifier implements INotifier {
|
|||
];
|
||||
|
||||
$subject = $l->t('{guest} (guest) mentioned group {group} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_team') {
|
||||
$richSubjectParameters['team'] = $this->getCircle($subjectParameters['sourceId']);
|
||||
$subject = $l->t('{guest} (guest) mentioned team {team} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_all') {
|
||||
$subject = $l->t('{guest} (guest) mentioned everyone in conversation {call}');
|
||||
} else {
|
||||
|
|
@ -821,6 +838,9 @@ class Notifier implements INotifier {
|
|||
];
|
||||
|
||||
$subject = $l->t('A guest mentioned group {group} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_team') {
|
||||
$richSubjectParameters['team'] = $this->getCircle($subjectParameters['sourceId']);
|
||||
$subject = $l->t('A guest mentioned team {team} in conversation {call}');
|
||||
} elseif ($notification->getSubject() === 'mention_all') {
|
||||
$subject = $l->t('A guest mentioned everyone in conversation {call}');
|
||||
} else {
|
||||
|
|
@ -1247,4 +1267,47 @@ class Notifier implements INotifier {
|
|||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
protected function getCircle(string $circleId): array {
|
||||
if (!$this->appManager->isEnabledForUser('circles')) {
|
||||
return [
|
||||
'type' => 'highlight',
|
||||
'id' => $circleId,
|
||||
'name' => $circleId,
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($this->circleNames[$circleId])) {
|
||||
$this->loadCircleDetails($circleId);
|
||||
}
|
||||
|
||||
if (!isset($this->circleNames[$circleId])) {
|
||||
return [
|
||||
'type' => 'highlight',
|
||||
'id' => $circleId,
|
||||
'name' => $circleId,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'circle',
|
||||
'id' => $circleId,
|
||||
'name' => $this->circleNames[$circleId],
|
||||
'link' => $this->circleLinks[$circleId],
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadCircleDetails(string $circleId): void {
|
||||
try {
|
||||
$circlesManager = Server::get(CirclesManager::class);
|
||||
$circlesManager->startSuperSession();
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
|
||||
$this->circleNames[$circleId] = $circle->getDisplayName();
|
||||
$this->circleLinks[$circleId] = $circle->getUrl();
|
||||
} catch (\Exception) {
|
||||
} finally {
|
||||
$circlesManager?->stopSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -719,16 +719,43 @@ class ParticipantService {
|
|||
|
||||
$circlesManager->startSession($federatedUser);
|
||||
try {
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
$circlesManager->stopSession();
|
||||
return $circle;
|
||||
return $circlesManager->getCircle($circleId);
|
||||
} catch (\Exception $e) {
|
||||
} finally {
|
||||
$circlesManager->stopSession();
|
||||
}
|
||||
|
||||
$circlesManager->stopSession();
|
||||
throw new ParticipantNotFoundException('Circle not found or not a member');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $circleId
|
||||
* @param string $userId
|
||||
* @return Member[]
|
||||
* @throws ParticipantNotFoundException
|
||||
*/
|
||||
public function getCircleMembers(string $circleId): array {
|
||||
try {
|
||||
$circlesManager = Server::get(CirclesManager::class);
|
||||
} catch (\Exception) {
|
||||
throw new ParticipantNotFoundException('Circle not found');
|
||||
}
|
||||
|
||||
$circlesManager->startSuperSession();
|
||||
try {
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
} catch (\Exception) {
|
||||
throw new ParticipantNotFoundException('Circle not found');
|
||||
} finally {
|
||||
$circlesManager->stopSession();
|
||||
}
|
||||
|
||||
$members = $circle->getInheritedMembers();
|
||||
return array_filter($members, static function (Member $member) {
|
||||
return $member->getUserType() === Member::TYPE_USER;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Room $room
|
||||
* @param Circle $circle
|
||||
|
|
|
|||
|
|
@ -13791,7 +13791,8 @@
|
|||
"circles",
|
||||
"emails",
|
||||
"federated_users",
|
||||
"phones"
|
||||
"phones",
|
||||
"teams"
|
||||
],
|
||||
"description": "Source of the participant"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13949,7 +13949,8 @@
|
|||
"circles",
|
||||
"emails",
|
||||
"federated_users",
|
||||
"phones"
|
||||
"phones",
|
||||
"teams"
|
||||
],
|
||||
"description": "Source of the participant"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@ export const MENTION = {
|
|||
// Parsed to another types
|
||||
FEDERATED_USER: 'federated_user',
|
||||
GROUP: 'group',
|
||||
TEAM: 'team',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7227,7 +7227,7 @@ export interface operations {
|
|||
* @default users
|
||||
* @enum {string}
|
||||
*/
|
||||
source?: "users" | "groups" | "circles" | "emails" | "federated_users" | "phones";
|
||||
source?: "users" | "groups" | "circles" | "emails" | "federated_users" | "phones" | "teams";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6811,7 +6811,7 @@ export interface operations {
|
|||
* @default users
|
||||
* @enum {string}
|
||||
*/
|
||||
source?: "users" | "groups" | "circles" | "emails" | "federated_users" | "phones";
|
||||
source?: "users" | "groups" | "circles" | "emails" | "federated_users" | "phones" | "teams";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1970,7 +1970,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then /^user "([^"]*)" adds (user|group|email|circle|federated_user|phone) "([^"]*)" to room "([^"]*)" with (\d+) \((v4)\)$/
|
||||
* @Then /^user "([^"]*)" adds (user|group|email|circle|federated_user|phone|team) "([^"]*)" to room "([^"]*)" with (\d+) \((v4)\)$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $newType
|
||||
|
|
@ -1990,6 +1990,10 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
}
|
||||
|
||||
if ($newType === 'circle' || $newType === 'team') {
|
||||
$newId = self::$createdTeams[$this->currentServer][$newId];
|
||||
}
|
||||
|
||||
$this->sendRequest(
|
||||
'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants',
|
||||
new TableNode([
|
||||
|
|
@ -2309,6 +2313,12 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
$message = str_replace('{$LOCAL_URL}', $this->localServerUrl, $message);
|
||||
$message = str_replace('{$LOCAL_REMOTE_URL}', $this->localRemoteServerUrl, $message);
|
||||
$message = str_replace('{$REMOTE_URL}', $this->remoteServerUrl, $message);
|
||||
if (str_contains($message, '@"TEAM_ID(')) {
|
||||
$result = preg_match('/TEAM_ID\(([^)]+)\)/', $message, $matches);
|
||||
if ($result) {
|
||||
$message = str_replace($matches[0], 'team/' . self::$createdTeams[$this->currentServer][$matches[1]], $message);
|
||||
}
|
||||
}
|
||||
|
||||
if ($message === '413 Payload Too Large') {
|
||||
$message .= "\n" . str_repeat('1', 32000);
|
||||
|
|
@ -3499,6 +3509,10 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
if (str_ends_with($row['mentionId'], '@{$REMOTE_URL}')) {
|
||||
$row['mentionId'] = str_replace('{$REMOTE_URL}', rtrim($this->remoteServerUrl, '/'), $row['mentionId']);
|
||||
}
|
||||
if ($row['source'] === 'teams') {
|
||||
[, $teamId] = explode('/', $row['mentionId'], 2);
|
||||
$row['id'] = $row['mentionId'] = 'team/' . self::getTeamIdForLabel($this->currentServer, $teamId);
|
||||
}
|
||||
if (array_key_exists('avatar', $row)) {
|
||||
Assert::assertMatchesRegularExpression('/' . self::$identifierToToken[$row['avatar']] . '\/avatar/', $mentions[$key]['avatar']);
|
||||
unset($row['avatar']);
|
||||
|
|
@ -3948,10 +3962,23 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
|
||||
private function assertNotifications($notifications, TableNode $formData) {
|
||||
Assert::assertCount(count($formData->getHash()), $notifications, 'Notifications count does not match:' . "\n" . json_encode($notifications, JSON_PRETTY_PRINT));
|
||||
Assert::assertEquals($formData->getHash(), array_map(function ($notification, $expectedNotification) {
|
||||
|
||||
$expectedNotifications = array_map(function (array $expectedNotification): array {
|
||||
if (str_contains($expectedNotification['object_id'], '/')) {
|
||||
[$roomToken, $message] = explode('/', $expectedNotification['object_id']);
|
||||
$result = preg_match('/TEAM_ID\(([^)]+)\)/', $message, $matches);
|
||||
if ($result) {
|
||||
$message = str_replace($matches[0], 'team/' . self::$createdTeams[$this->currentServer][$matches[1]], $message);
|
||||
}
|
||||
$expectedNotification['object_id'] = $roomToken . '/' . $message;
|
||||
}
|
||||
return $expectedNotification;
|
||||
}, $formData->getHash());
|
||||
|
||||
Assert::assertEquals($expectedNotifications, array_map(function ($notification, $expectedNotification) {
|
||||
$data = [];
|
||||
if (isset($expectedNotification['object_id'])) {
|
||||
if (strpos($notification['object_id'], '/') !== false) {
|
||||
if (str_contains($notification['object_id'], '/')) {
|
||||
[$roomToken, $message] = explode('/', $notification['object_id']);
|
||||
$messageText = self::$messageIdToText[$message] ?? 'UNKNOWN_MESSAGE';
|
||||
|
||||
|
|
@ -3976,6 +4003,10 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
if ($result && isset(self::$identifierToToken[$matches[1]])) {
|
||||
$data['message'] = str_replace(self::$identifierToToken[$matches[1]], $matches[0], $data['message']);
|
||||
}
|
||||
$result = preg_match('/TEAM_ID\(([^)]+)\)/', $expectedNotification['message'], $matches);
|
||||
if ($result) {
|
||||
$data['message'] = str_replace($matches[0], self::$createdTeams[$this->currentServer][$matches[1]], $data['message']);
|
||||
}
|
||||
}
|
||||
if (isset($expectedNotification['object_type'])) {
|
||||
$data['object_type'] = (string)$notification['object_type'];
|
||||
|
|
@ -3985,7 +4016,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
return $data;
|
||||
}, $notifications, $formData->getHash()), json_encode($notifications, JSON_PRETTY_PRINT));
|
||||
}, $notifications, $expectedNotifications), json_encode($notifications, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -5232,7 +5263,4 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
Assert::assertEquals($statusCode, $response->getStatusCode(), $message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,6 +364,82 @@ Feature: chat/notifications
|
|||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
|
||||
|
||||
Scenario: Team-mention when recipient is online in the group room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant2" to team "1234"
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds team "1234" to room "room" with 200 (v4)
|
||||
Given user "participant2" joins room "room" with 200 (v4)
|
||||
When user "participant1" sends message 'Hi @"TEAM_ID(1234)" bye' to room "room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
| spreed | chat | room/Hi @"TEAM_ID(1234)" bye | participant1-displayname mentioned team 1234 in conversation room |
|
||||
|
||||
Scenario: Team-mention when team is not a member of the room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant2" to team "1234"
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds user "participant2" to room "room" with 200 (v4)
|
||||
Given user "participant2" joins room "room" with 200 (v4)
|
||||
When user "participant1" sends message 'Hi @"TEAM_ID(1234)" bye' to room "room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
|
||||
Scenario: Team-mention when recipient is offline in the group room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant2" to team "1234"
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds team "1234" to room "room" with 200 (v4)
|
||||
# Join and leave to clear the invite notification
|
||||
Given user "participant2" joins room "room" with 200 (v4)
|
||||
Given user "participant2" leaves room "room" with 200 (v4)
|
||||
When user "participant1" sends message 'Hi @"TEAM_ID(1234)" bye' to room "room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
| spreed | chat | room/Hi @"TEAM_ID(1234)" bye | participant1-displayname mentioned team 1234 in conversation room |
|
||||
|
||||
Scenario: Silent team-mention when recipient is offline in the group room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant2" to team "1234"
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds team "1234" to room "room" with 200 (v4)
|
||||
# Join and leave to clear the invite notification
|
||||
Given user "participant2" joins room "room" with 200 (v4)
|
||||
Given user "participant2" leaves room "room" with 200 (v4)
|
||||
When user "participant1" silent sends message 'Hi @"TEAM_ID(1234)" bye' to room "room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
|
||||
Scenario: Team-mention when recipient with disabled notifications in the group room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant2" to team "1234"
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds team "1234" to room "room" with 200 (v4)
|
||||
# Join and leave to clear the invite notification
|
||||
Given user "participant2" joins room "room" with 200 (v4)
|
||||
Given user "participant2" leaves room "room" with 200 (v4)
|
||||
And user "participant2" sets notifications to disabled for room "room" (v4)
|
||||
When user "participant1" sends message 'Hi @"TEAM_ID(1234)" bye' to room "room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject |
|
||||
|
||||
|
||||
Scenario: Replying with all mention types only gives a reply notification
|
||||
When user "participant1" creates room "room" (v4)
|
||||
| roomType | 2 |
|
||||
|
|
|
|||
|
|
@ -74,11 +74,14 @@ Feature: chat/mentions
|
|||
| participant2 | participant2-displayname | users | participant2 |
|
||||
|
||||
Scenario: get matched mentions in a group room
|
||||
Given team "1234" exists
|
||||
Given add user "participant1" to team "1234"
|
||||
Given add user "participant3" to team "1234"
|
||||
When user "participant1" creates room "group room" (v4)
|
||||
| roomType | 2 |
|
||||
| roomName | room |
|
||||
And user "participant1" adds user "participant2" to room "group room" with 200 (v4)
|
||||
And user "participant1" adds user "participant3" to room "group room" with 200 (v4)
|
||||
And user "participant1" adds team "1234" to room "group room" with 200 (v4)
|
||||
Then user "participant1" gets the following candidate mentions in room "group room" for "part" with 200
|
||||
| id | label | source | mentionId |
|
||||
| participant2 | participant2-displayname | users | participant2 |
|
||||
|
|
@ -91,6 +94,15 @@ Feature: chat/mentions
|
|||
| id | label | source | mentionId |
|
||||
| participant1 | participant1-displayname | users | participant1 |
|
||||
| participant2 | participant2-displayname | users | participant2 |
|
||||
And user "participant1" gets the following candidate mentions in room "group room" for "1234" with 200
|
||||
| id | label | source | mentionId |
|
||||
| 1234 | 1234 | teams | team/1234 |
|
||||
And user "participant2" gets the following candidate mentions in room "group room" for "1234" with 200
|
||||
| id | label | source | mentionId |
|
||||
| 1234 | 1234 | teams | team/1234 |
|
||||
And user "participant3" gets the following candidate mentions in room "group room" for "1234" with 200
|
||||
| id | label | source | mentionId |
|
||||
| 1234 | 1234 | teams | team/1234 |
|
||||
|
||||
Scenario: get unmatched mentions in a group room
|
||||
When user "participant1" creates room "group room" (v4)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use OCA\Talk\Participant;
|
|||
use OCA\Talk\Room;
|
||||
use OCA\Talk\Service\AvatarService;
|
||||
use OCA\Talk\Service\ParticipantService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\Federation\ICloudId;
|
||||
|
|
@ -28,6 +29,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
|||
use Test\TestCase;
|
||||
|
||||
class UserMentionTest extends TestCase {
|
||||
protected IAppManager&MockObject $appManager;
|
||||
protected ICommentsManager&MockObject $commentsManager;
|
||||
protected IUserManager&MockObject $userManager;
|
||||
protected IGroupManager&MockObject $groupManager;
|
||||
|
|
@ -42,6 +44,7 @@ class UserMentionTest extends TestCase {
|
|||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->appManager = $this->createMock(IAppManager::class);
|
||||
$this->commentsManager = $this->createMock(ICommentsManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
|
|
@ -52,6 +55,7 @@ class UserMentionTest extends TestCase {
|
|||
$this->l = $this->createMock(IL10N::class);
|
||||
|
||||
$this->parser = new UserMention(
|
||||
$this->appManager,
|
||||
$this->commentsManager,
|
||||
$this->userManager,
|
||||
$this->groupManager,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use OCA\Talk\Participant;
|
|||
use OCA\Talk\Room;
|
||||
use OCA\Talk\Service\AvatarService;
|
||||
use OCA\Talk\Service\ParticipantService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
|
|
@ -49,6 +50,7 @@ class NotifierTest extends TestCase {
|
|||
protected IFactory&MockObject $lFactory;
|
||||
protected IURLGenerator&MockObject $url;
|
||||
protected Config&MockObject $config;
|
||||
protected IAppManager&MockObject $appManager;
|
||||
protected IUserManager&MockObject $userManager;
|
||||
protected IGroupManager&MockObject $groupManager;
|
||||
protected GuestManager&MockObject $guestManager;
|
||||
|
|
@ -75,6 +77,7 @@ class NotifierTest extends TestCase {
|
|||
$this->lFactory = $this->createMock(IFactory::class);
|
||||
$this->url = $this->createMock(IURLGenerator::class);
|
||||
$this->config = $this->createMock(Config::class);
|
||||
$this->appManager = $this->createMock(IAppManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->guestManager = $this->createMock(GuestManager::class);
|
||||
|
|
@ -98,6 +101,7 @@ class NotifierTest extends TestCase {
|
|||
$this->lFactory,
|
||||
$this->url,
|
||||
$this->config,
|
||||
$this->appManager,
|
||||
$this->userManager,
|
||||
$this->groupManager,
|
||||
$this->guestManager,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue