Adjust approach for search results for listable rooms

Added "/listable-room" endpoint for listing listable rooms with a similar
result format like the "/room" endpoint.
Switched left sidebar to use Conversation components for displaying
results.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
This commit is contained in:
Vincent Petry 2020-12-02 12:22:01 +01:00
Родитель f4d26223b3
Коммит 353206e1e6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E055D6A4D513575C
8 изменённых файлов: 162 добавлений и 41 удалений

Просмотреть файл

@ -183,6 +183,14 @@ return [
'apiVersion' => 'v(1|2|3)',
],
],
[
'name' => 'Room#getListedRooms',
'url' => '/api/{apiVersion}/listed-room',
'verb' => 'GET',
'requirements' => [
'apiVersion' => 'v3',
],
],
[
'name' => 'Room#createRoom',
'url' => '/api/{apiVersion}/room',

Просмотреть файл

@ -4,6 +4,63 @@
* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`
* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`
## Get listed conversations
* Method: `GET`
* Endpoint: `/listed-room`
* Response:
- Status code:
+ `200 OK`
+ `401 Unauthorized` when the user is not logged in
- Header:
field | type | Description
------|------|------------
`searchTerm` | string | search term
- Data:
Array of conversations, each conversation has at least:
field | type | API | Description
------|------|-----|------------
`token` | string | * | Token identifier of the conversation which is used for further interaction
`type` | int | * | See list of conversation types in the [constants list](constants.md#Conversation-types)
`name` | string | * | Name of the conversation (can also be empty)
`displayName` | string | * | `name` if non empty, otherwise it falls back to a list of participants
`participantType` | int | * | Permissions level of the current user
`attendeeId` | int | v3 | Unique attendee id
`attendeePin` | string | v3 | Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)
`actorType` | string | v3 | Currently known `users|guests|emails|groups`
`actorId` | string | v3 | The unique identifier for the given actor type
`participantInCall` | bool | 🏴 v1 | Flag if the current user is in the call (deprecated, use `participantFlags` instead)
`participantFlags` | int | * | Flags of the current user (only available with `in-call-flags` capability)
`readOnly` | int | * | Read-only state for the current user (only available with `read-only-rooms` capability)
`listable` | int | * | Listable scope for the room (only available with `listable-rooms` capability)
`count` | int | 🏴 v1 | **Deprecated:** ~~Number of active users~~ - always returns `0`
`numGuests` | int | 🏴 v1 | Number of active guests
`lastPing` | int | * | Timestamp of the last ping of the current user (should be used for sorting)
`sessionId` | string | * | `'0'` if not connected, otherwise a 512 character long string
`hasPassword` | bool | * | Flag if the conversation has a password
`hasCall` | bool | * | Flag if the conversation has an active call
`canStartCall` | bool | * | Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)
`canDeleteConversation` | bool | 🆕 v2 | Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)
`canLeaveConversation` | bool | 🆕 v2 | Flag if the user can leave the conversation (not possible for the last user with moderator permissions)
`lastActivity` | int | * | Timestamp of the last activity in the conversation, in seconds and UTC time zone
`isFavorite` | bool | * | Flag if the conversation is favorited by the user
`notificationLevel` | int | * | The notification level for the user (one of `Participant::NOTIFY_*` (1-3))
`lobbyState` | int | * | Webinary lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability)
`lobbyTimer` | int | * | Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
`sipEnabled` | int | v3 | SIP enable status (0-1)
`canEnableSIP` | int | v3 | Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation
`unreadMessages` | int | * | Number of unread chat messages in the conversation (only available with `chat-v2` capability)
`unreadMention` | bool | * | Flag if the user was mentioned since their last visit
`lastReadMessage` | int | * | ID of the last read message in a room (only available with `chat-read-marker` capability)
`lastMessage` | message | * | Last message in a conversation if available, otherwise empty
`objectType` | string | * | The type of object that the conversation is associated with; "share:password" if the conversation is used to request a password for a share, otherwise empty
`objectId` | string | * | Share token if "objectType" is "share:password", otherwise empty
## Get user´s conversations
* Method: `GET`

Просмотреть файл

@ -12,6 +12,11 @@ Explanations:
* Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_ROOMS_GET`
* Since: 8.0.0
### Search listed conversations
* Event class: `OCA\Talk\Events\UserEvent`
* Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_LISTED_ROOMS_GET`
* Since: 11.0.0
### Create conversation

Просмотреть файл

@ -68,6 +68,7 @@ use OCP\UserStatus\IUserStatus;
class RoomController extends AEnvironmentAwareController {
public const EVENT_BEFORE_ROOMS_GET = self::class . '::preGetRooms';
public const EVENT_BEFORE_LISTED_ROOMS_GET = self::class . '::preGetListedRooms';
/** @var string|null */
protected $userId;
@ -219,6 +220,37 @@ class RoomController extends AEnvironmentAwareController {
return new DataResponse($return, Http::STATUS_OK, $this->getTalkHashHeader());
}
/**
* Search listed rooms
*
* @NoAdminRequired
*
* @param string $searchTerm search term
* @return DataResponse
*/
public function getListedRooms(string $searchTerm = ''): DataResponse {
$event = new UserEvent($this->userId);
$this->dispatcher->dispatch(self::EVENT_BEFORE_LISTED_ROOMS_GET, $event);
$rooms = $this->manager->getListedRoomsForUser($this->userId, $searchTerm);
$return = [];
foreach ($rooms as $room) {
try {
$roomData = $this->formatRoom($room, null);
// since formatRoom will break early due to having no participant,
// we populate the remaining base attributes here
$return[] = $this->populateBaseRoomData($roomData, $room, $this->userId);
// TODO: should we populate more ?
} catch (RoomNotFoundException $e) {
} catch (\RuntimeException $e) {
}
}
return new DataResponse($return, Http::STATUS_OK);
}
/**
* @PublicPage
*
@ -599,20 +631,12 @@ class RoomController extends AEnvironmentAwareController {
$attendee = $currentParticipant->getAttendee();
$userId = $attendee->getActorType() === Attendee::ACTOR_USERS ? $attendee->getActorId() : '';
$roomData = $this->populateBaseRoomData($roomData, $room, $userId);
$roomData = array_merge($roomData, [
'name' => $room->getName(),
'displayName' => $room->getDisplayName($userId),
'objectType' => $room->getObjectType(),
'objectId' => $room->getObjectId(),
'participantType' => $attendee->getParticipantType(),
'readOnly' => $room->getReadOnly(),
'listable' => $room->getListable(),
'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
'lastActivity' => $lastActivity,
'isFavorite' => $attendee->isFavorite(),
'notificationLevel' => $attendee->getNotificationLevel(),
'lobbyState' => $room->getLobbyState(),
'lobbyTimer' => $lobbyTimer,
]);
if ($this->getAPIVersion() >= 3) {
if ($this->talkConfig->isSIPConfigured()) {
@ -732,6 +756,33 @@ class RoomController extends AEnvironmentAwareController {
return $roomData;
}
protected function populateBaseRoomData(array $roomData, Room $room, $userId) {
$lastActivity = $room->getLastActivity();
if ($lastActivity instanceof \DateTimeInterface) {
$lastActivity = $lastActivity->getTimestamp();
} else {
$lastActivity = 0;
}
$lobbyTimer = $room->getLobbyTimer();
if ($lobbyTimer instanceof \DateTimeInterface) {
$lobbyTimer = $lobbyTimer->getTimestamp();
} else {
$lobbyTimer = 0;
}
return array_merge($roomData, [
'name' => $room->getName(),
'displayName' => $room->getDisplayName($userId),
'objectType' => $room->getObjectType(),
'objectId' => $room->getObjectId(),
'readOnly' => $room->getReadOnly(),
'listable' => $room->getListable(),
'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
'lobbyState' => $room->getLobbyState(),
]);
}
/**
* @param Room $room
* @param Participant $participant

Просмотреть файл

@ -347,7 +347,7 @@ class Manager {
$allowedRoomTypes = [Room::GROUP_CALL, Room::PUBLIC_CALL];
$allowedListedTypes = [Room::LISTABLE_ALL];
if (!$this->isGuestUser($userId)) {
$listedType[] = Room::LISTABLE_USERS;
$allowedListedTypes[] = Room::LISTABLE_USERS;
}
$query = $this->db->getQueryBuilder();
$query->select('r.*')

Просмотреть файл

@ -45,7 +45,7 @@
:highlighted="counterShouldBePrimary">
<strong>{{ item.unreadMessages }}</strong>
</AppNavigationCounter>
<template slot="actions">
<template v-if="!isSearchResult" slot="actions">
<ActionButton v-if="canFavorite"
:icon="iconFavorite"
@click.prevent.exact="toggleFavoriteConversation">
@ -124,6 +124,10 @@ export default {
ConversationIcon,
},
props: {
isSearchResult: {
type: Boolean,
default: false,
},
item: {
type: Object,
default: function() {

Просмотреть файл

@ -47,12 +47,11 @@
<template v-if="searchResultsListedConversations.length !== 0">
<Caption
:title="t('spreed', 'Listed conversations')" />
<li role="presentation">
<!-- FIXME: use ConversationsList instead ? -->
<ConversationsOptionsList
:items="searchResultsListedConversations"
@click="joinConversation" />
</li>
<Conversation
v-for="item of searchResultsListedConversations"
:key="item.id"
:item="item"
:is-search-result="true" />
</template>
<template v-if="searchResultsUsers.length !== 0">
<Caption
@ -114,6 +113,7 @@
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import Caption from '../Caption'
import ConversationsList from './ConversationsList/ConversationsList'
import Conversation from './ConversationsList/Conversation'
import ConversationsOptionsList from '../ConversationsOptionsList'
import Hint from '../Hint'
import SearchBox from './SearchBox/SearchBox'
@ -144,6 +144,7 @@ export default {
Hint,
SearchBox,
NewGroupConversation,
Conversation,
},
mixins: [
@ -286,18 +287,7 @@ export default {
async fetchListedConversations() {
this.listedConversationsLoading = true
const response = await searchListedConversations(this.searchText)
if (response.data.ocs.data?.entries?.length) {
this.searchResultsListedConversations = response.data.ocs.data.entries.map((result) => {
return {
// TODO: extract token ?
id: result.resourceUrl,
label: result.title,
icon: result.icon,
}
})
} else {
this.searchResultsListedConversations = []
}
this.searchResultsListedConversations = response.data.ocs.data
this.listedConversationsLoading = false
},
@ -308,10 +298,6 @@ export default {
this.focusInitialise()
},
async joinConversation(item) {
console.log('TODO: Implement join conversation: ', item)
},
/**
* Create a new conversation with the selected group/user/circle
* @param {Object} item The autocomplete suggestion to start a conversation with

Просмотреть файл

@ -90,19 +90,29 @@ const checkTalkVersionHash = function(response) {
/**
* Fetch listed conversations
* @param {string} searchText The string that will be used in the search query.
* @param {string} searchTerm The string that will be used in the search query.
*/
const searchListedConversations = async function(searchText) {
const searchListedConversations = async function(searchTerm) {
try {
// use search provider to find listed conversations
return axios.get(generateOcsUrl('search/providers/talk-listed-conversations', 2) + 'search', {
const response = await axios.get(generateOcsUrl('apps/spreed/api/v3', 2) + 'listed-room', {
params: {
term: searchText,
format: 'json',
searchTerm,
},
})
if (maintenanceWarning) {
maintenanceWarning.hideToast()
maintenanceWarning = null
}
return response
} catch (error) {
console.debug('Error while searching listedk conversations: ', error)
if (error.response && error.response.status === 503 && !maintenanceWarning) {
maintenanceWarning = showError(t('spreed', 'Nextcloud is in maintenance mode, please reload the page'), {
timeout: TOAST_PERMANENT_TIMEOUT,
})
}
throw error
}
}
/**