Skip to content

Commit

Permalink
Merge pull request #13699 from nextcloud/fix/4937/invite-emails-to-co…
Browse files Browse the repository at this point in the history
…nversation
  • Loading branch information
Antreesy authored Nov 6, 2024
2 parents 7e16c22 + 002c5a7 commit 9cb29bf
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 62 deletions.
9 changes: 6 additions & 3 deletions src/components/LeftSidebar/LeftSidebar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import { loadState } from '@nextcloud/initial-state'
import LeftSidebar from './LeftSidebar.vue'

import router from '../../__mocks__/router.js'
import { searchPossibleConversations, searchListedConversations } from '../../services/conversationsService.js'
import { searchListedConversations } from '../../services/conversationsService.js'
import { autocompleteQuery } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
import storeConfig from '../../store/storeConfig.js'
import { findNcListItems, findNcActionButton, findNcButton } from '../../test-helpers.js'
import { requestTabLeadership } from '../../utils/requestTabLeadership.js'

jest.mock('../../services/conversationsService', () => ({
searchPossibleConversations: jest.fn(),
searchListedConversations: jest.fn(),
}))
jest.mock('../../services/coreService', () => ({
autocompleteQuery: jest.fn(),
}))

// short-circuit debounce
jest.mock('debounce', () => jest.fn().mockImplementation(fn => fn))
Expand Down Expand Up @@ -296,7 +299,7 @@ describe('LeftSidebar.vue', () => {
* @param {object} loadStateSettingsOverride Allows to override some properties
*/
async function testSearch(searchTerm, possibleResults, listedResults, loadStateSettingsOverride) {
searchPossibleConversations.mockResolvedValue({
autocompleteQuery.mockResolvedValue({
data: {
ocs: {
data: possibleResults,
Expand Down
6 changes: 3 additions & 3 deletions src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,9 @@ import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManage
import {
createPrivateConversation,
fetchNoteToSelfConversation,
searchPossibleConversations,
searchListedConversations,
} from '../../services/conversationsService.js'
import { autocompleteQuery } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
import { talkBroadcastChannel } from '../../services/talkBroadcastChannel.js'
import { useFederationStore } from '../../stores/federation.ts'
Expand Down Expand Up @@ -737,12 +737,12 @@ export default {
try {
// FIXME: move to conversationsStore
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
const { request, cancel } = CancelableRequest(autocompleteQuery)
this.cancelSearchPossibleConversations = cancel
const response = await request({
searchText: this.searchText,
token: undefined,
token: 'new',
onlyUsers: !this.canStartConversations,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ import DialpadPanel from '../UIShared/DialpadPanel.vue'
import TransitionWrapper from '../UIShared/TransitionWrapper.vue'
import { useArrowNavigation } from '../../composables/useArrowNavigation.js'
import { searchPossibleConversations } from '../../services/conversationsService.js'
import { SHARE } from '../../constants.js'
import { autocompleteQuery } from '../../services/coreService.ts'
import CancelableRequest from '../../utils/cancelableRequest.js'
export default {
Expand Down Expand Up @@ -205,10 +206,14 @@ export default {
this.contactsLoading = true
try {
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
const { request, cancel } = CancelableRequest(autocompleteQuery)
this.cancelSearchPossibleConversations = cancel
const response = await request({ searchText: this.searchText })
const response = await request({
searchText: this.searchText,
token: 'new',
forceTypes: [SHARE.TYPE.EMAIL], // e-mail guests are allowed directly after conversation creation
})
this.searchResults = response?.data?.ocs?.data || []
if (this.searchResults.length === 0) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/RightSidebar/Participants/ParticipantsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ import { useIsInCall } from '../../../composables/useIsInCall.js'
import { useSortParticipants } from '../../../composables/useSortParticipants.js'
import { ATTENDEE } from '../../../constants.js'
import { getTalkConfig, hasTalkFeature } from '../../../services/CapabilitiesManager.ts'
import { searchPossibleConversations } from '../../../services/conversationsService.js'
import { autocompleteQuery } from '../../../services/coreService.ts'
import { EventBus } from '../../../services/EventBus.ts'
import { addParticipant } from '../../../services/participantsService.js'
import { useSidebarStore } from '../../../stores/sidebar.js'
Expand Down Expand Up @@ -279,7 +279,7 @@ export default {
this.resetNavigation()
try {
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
const { request, cancel } = CancelableRequest(autocompleteQuery)
this.cancelSearchPossibleConversations = cancel
const response = await request({
Expand Down
6 changes: 5 additions & 1 deletion src/services/CapabilitiesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,18 @@ export function hasTalkFeature(token: string = 'local', feature: string): boolea
* @param key1 top-level key (e.g. 'attachments')
* @param key2 second-level key (e.g. 'allowed')
*/
export function getTalkConfig(token: string = 'local', key1: keyof Config, key2: keyof Config[keyof Config]) {
export function getTalkConfig(token: string = 'local', key1: keyof Config, key2: string) {
const remoteCapabilities = getRemoteCapability(token)
const locals = localCapabilities?.spreed?.config?.[key1]
if (localCapabilities?.spreed?.['config-local']?.[key1]?.includes(key2)) {
// @ts-expect-error Vue: Element implicitly has an any type because expression of type string can't be used to index type
return localCapabilities?.spreed?.config?.[key1]?.[key2]
} else if (token === 'local' || !remoteCapabilities) {
// @ts-expect-error Vue: Element implicitly has an any type because expression of type string can't be used to index type
return localCapabilities?.spreed?.config?.[key1]?.[key2]
} else {
// TODO discuss handling remote config (respect remote only / both / minimal)
// @ts-expect-error Vue: Element implicitly has an any type because expression of type string can't be used to index type
return remoteCapabilities?.spreed?.config?.[key1]?.[key2]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'

import { searchPossibleConversations } from './conversationsService.js'
import { SHARE } from '../constants.js'
import { SHARE } from '../../constants.js'
import { autocompleteQuery } from '../coreService.ts'

jest.mock('@nextcloud/axios', () => ({
get: jest.fn(),
Expand All @@ -24,7 +24,7 @@ jest.mock('@nextcloud/capabilities', () => ({
}))
}))

describe('conversationsService', () => {
describe('coreService', () => {
afterEach(() => {
// cleaning up the mess left behind the previous test
jest.clearAllMocks()
Expand All @@ -35,8 +35,8 @@ describe('conversationsService', () => {
* @param {boolean} onlyUsers Whether or not to only search for users
* @param {Array} expectedShareTypes The expected search types to look for
*/
function testSearchPossibleConversations(token, onlyUsers, expectedShareTypes) {
searchPossibleConversations(
function testAutocompleteQuery(token, onlyUsers, expectedShareTypes) {
autocompleteQuery(
{
searchText: 'search-text',
token,
Expand All @@ -60,8 +60,8 @@ describe('conversationsService', () => {
)
}

test('searchPossibleConversations with only users', () => {
testSearchPossibleConversations(
test('autocompleteQuery with only users', () => {
testAutocompleteQuery(
'conversation-token',
true,
[
Expand All @@ -70,8 +70,8 @@ describe('conversationsService', () => {
)
})

test('searchPossibleConversations with other share types', () => {
testSearchPossibleConversations(
test('autocompleteQuery with other share types', () => {
testAutocompleteQuery(
'conversation-token',
false,
[
Expand All @@ -84,8 +84,8 @@ describe('conversationsService', () => {
)
})

test('searchPossibleConversations with other share types and a new token', () => {
testSearchPossibleConversations(
test('autocompleteQuery with other share types and a new token', () => {
testAutocompleteQuery(
'new',
false,
[
Expand Down
40 changes: 1 addition & 39 deletions src/services/conversationsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'

import { getTalkConfig, hasTalkFeature } from './CapabilitiesManager.ts'
import { ATTENDEE, CONVERSATION, SHARE } from '../constants.js'

const canInviteToFederation = hasTalkFeature('local', 'federation-v1')
&& getTalkConfig('local', 'federation', 'enabled')
&& getTalkConfig('local', 'federation', 'outgoing-enabled')
import { ATTENDEE, CONVERSATION } from '../constants.js'

/**
* Fetches the conversations from the server.
Expand Down Expand Up @@ -70,38 +65,6 @@ const fetchNoteToSelfConversation = async function() {
return axios.get(generateOcsUrl('apps/spreed/api/v4/room/note-to-self'))
}

/**
* Fetch possible conversations
*
* @param {object} data the wrapping object;
* @param {string} data.searchText The string that will be used in the search query.
* @param {string} [data.token] The token of the conversation (if any), or "new" for a new one
* @param {boolean} [data.onlyUsers] Only return users
* @param {object} options options
*/
const searchPossibleConversations = async function({ searchText, token, onlyUsers }, options) {
token = token || 'new'
onlyUsers = !!onlyUsers

const shareTypes = [
SHARE.TYPE.USER,
!onlyUsers ? SHARE.TYPE.GROUP : null,
!onlyUsers ? SHARE.TYPE.CIRCLE : null,
(!onlyUsers && token !== 'new') ? SHARE.TYPE.EMAIL : null,
(!onlyUsers && canInviteToFederation) ? SHARE.TYPE.REMOTE : null,
].filter(type => type !== null)

return axios.get(generateOcsUrl('core/autocomplete/get'), {
...options,
params: {
search: searchText,
itemType: 'call',
itemId: token,
shareTypes,
},
})
}

/**
* Create a new one to one conversation with the specified user.
*
Expand Down Expand Up @@ -390,7 +353,6 @@ export {
fetchNoteToSelfConversation,
getUpcomingEvents,
searchListedConversations,
searchPossibleConversations,
createOneToOneConversation,
createGroupConversation,
createPrivateConversation,
Expand Down
57 changes: 57 additions & 0 deletions src/services/coreService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'

import { getTalkConfig, hasTalkFeature } from './CapabilitiesManager.ts'
import { SHARE } from '../constants.js'

const canInviteToFederation = hasTalkFeature('local', 'federation-v1')
&& getTalkConfig('local', 'federation', 'enabled')
&& getTalkConfig('local', 'federation', 'outgoing-enabled')

type SearchPayload = {
searchText: string
token?: string | 'new'
onlyUsers?: boolean
forceTypes?: typeof SHARE.TYPE[keyof typeof SHARE.TYPE][]
}

/**
* Fetch possible conversations
*
* @param payload the wrapping object;
* @param payload.searchText The string that will be used in the search query.
* @param [payload.token] The token of the conversation (if any) | 'new' for new conversations
* @param [payload.onlyUsers] Whether to return only registered users
* @param [payload.forceTypes] Whether to force some types to be included in query
* @param options options
*/
const autocompleteQuery = async function({ searchText, token = 'new', onlyUsers = false, forceTypes = [] }: SearchPayload, options: object) {
const shareTypes = onlyUsers
? [SHARE.TYPE.USER].concat(forceTypes)
: [
SHARE.TYPE.USER,
SHARE.TYPE.GROUP,
SHARE.TYPE.CIRCLE,
token !== 'new' ? SHARE.TYPE.EMAIL : null,
canInviteToFederation ? SHARE.TYPE.REMOTE : null,
].filter(type => type !== null).concat(forceTypes)

return axios.get(generateOcsUrl('core/autocomplete/get'), {
...options,
params: {
search: searchText,
itemType: 'call',
itemId: token,
shareTypes,
},
})
}

export {
autocompleteQuery,
}

0 comments on commit 9cb29bf

Please sign in to comment.