Skip to content

Commit

Permalink
Cancel search requests while typing or exiting
Browse files Browse the repository at this point in the history
The search requests in the left sidebar, new conversation dialog and
participants tab are now cancellable. They cancel either when the user
continues to type or when exiting or destroying the component.

This should help reduce the load on servers while people are searching.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
  • Loading branch information
PVince81 committed Dec 14, 2020
1 parent af69e3b commit 235f5d9
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 44 deletions.
77 changes: 62 additions & 15 deletions src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
</template>

<script>
import CancelableRequest from '../../utils/cancelableRequest'
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import Caption from '../Caption'
import ConversationsList from './ConversationsList/ConversationsList'
Expand All @@ -131,6 +132,7 @@ import { CONVERSATION } from '../../constants'
import { loadState } from '@nextcloud/initial-state'
import NewGroupConversation from './NewGroupConversation/NewGroupConversation'
import arrowNavigation from '../../mixins/arrowNavigation'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'

export default {
Expand Down Expand Up @@ -165,6 +167,8 @@ export default {
isCirclesEnabled: loadState('talk', 'circles_enabled'),
canStartConversations: loadState('talk', 'start_conversations'),
initialisedConversations: false,
cancelSearchPossibleConversations: () => {},
cancelSearchListedConversations: () => {},
}
},

Expand Down Expand Up @@ -231,7 +235,7 @@ export default {
* After a conversation was created, the search filter is reset
*/
EventBus.$once('resetSearchFilter', () => {
this.searchText = ''
this.abortSearch()
})

this.fetchConversations()
Expand All @@ -252,6 +256,12 @@ export default {

beforeDestroy() {
EventBus.$off('shouldRefreshConversations', this.debounceFetchConversations)

this.cancelSearchPossibleConversations()
this.cancelSearchPossibleConversations = null

this.cancelSearchListedConversations()
this.cancelSearchListedConversations = null
},

methods: {
Expand All @@ -273,23 +283,54 @@ export default {

async fetchPossibleConversations() {
this.contactsLoading = true
const response = await searchPossibleConversations(this.searchText, undefined, !this.canStartConversations)
this.searchResults = response.data.ocs.data
this.searchResultsUsers = this.searchResults.filter((match) => {
return match.source === 'users'
&& match.id !== this.$store.getters.getUserId()
&& !this.hasOneToOneConversationWith(match.id)
})
this.searchResultsGroups = this.searchResults.filter((match) => match.source === 'groups')
this.searchResultsCircles = this.searchResults.filter((match) => match.source === 'circles')
this.contactsLoading = false

try {
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
this.cancelSearchPossibleConversations = cancel

const response = await request({
searchText: this.searchText,
token: undefined,
onlyUsers: !this.canStartConversations,
})

this.searchResults = response?.data?.ocs?.data || []
this.searchResultsUsers = this.searchResults.filter((match) => {
return match.source === 'users'
&& match.id !== this.$store.getters.getUserId()
&& !this.hasOneToOneConversationWith(match.id)
})
this.searchResultsGroups = this.searchResults.filter((match) => match.source === 'groups')
this.searchResultsCircles = this.searchResults.filter((match) => match.source === 'circles')
this.contactsLoading = false
} catch (exception) {
if (CancelableRequest.isCancel(exception)) {
return
}
console.error('Error searching for possible conversations', exception)
showError(t('spreed', 'An error occurred while performing the search'))
}
},

async fetchListedConversations() {
this.listedConversationsLoading = true
const response = await searchListedConversations(this.searchText)
this.searchResultsListedConversations = response.data.ocs.data
this.listedConversationsLoading = false
try {
this.listedConversationsLoading = true

this.cancelSearchListedConversations('canceled')
const { request, cancel } = CancelableRequest(searchListedConversations)
this.cancelSearchListedConversations = cancel

const response = await request({ searchText: this.searchText })
this.searchResultsListedConversations = response.data.ocs.data
this.listedConversationsLoading = false
} catch (exception) {
if (CancelableRequest.isCancel(exception)) {
return
}
console.error('Error searching for listed conversations', exception)
showError(t('spreed', 'An error occurred while performing the search'))
}
},

async fetchSearchResults() {
Expand Down Expand Up @@ -332,6 +373,12 @@ export default {
// Reset the search text, therefore end the search operation.
abortSearch() {
this.searchText = ''
if (this.cancelSearchPossibleConversations) {
this.cancelSearchPossibleConversations()
}
if (this.cancelSearchListedConversations) {
this.cancelSearchListedConversations()
}
},

showSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
</template>

<script>
import CancelableRequest from '../../../../utils/cancelableRequest'
import debounce from 'debounce'
import { showError } from '@nextcloud/dialogs'
import { searchPossibleConversations } from '../../../../services/conversationsService'
Expand Down Expand Up @@ -82,6 +83,7 @@ export default {
// with an empty screen as search text.
contactsLoading: true,
noResults: false,
cancelSearchPossibleConversations: () => {},
}
},

Expand Down Expand Up @@ -111,6 +113,11 @@ export default {
this.contactsLoading = false
},

beforeDestroy() {
this.cancelSearchPossibleConversations()
this.cancelSearchPossibleConversations = null
},

methods: {
handleInput() {
this.noResults = false
Expand All @@ -125,13 +132,21 @@ export default {

async fetchSearchResults() {
try {
const response = await searchPossibleConversations(this.searchText)
this.searchResults = response.data.ocs.data
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
this.cancelSearchPossibleConversations = cancel

const response = await request({ searchText: this.searchText })

this.searchResults = response?.data?.ocs?.data || []
this.contactsLoading = false
if (this.searchResults.length === 0) {
this.noResults = true
}
} catch (exception) {
if (CancelableRequest.isCancel(exception)) {
return
}
console.error(exception)
showError(t('spreed', 'An error occurred while performing the search'))
}
Expand Down
26 changes: 22 additions & 4 deletions src/components/RightSidebar/Participants/ParticipantsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default {
* Stores the cancel function for cancelableGetParticipants
*/
cancelGetParticipants: () => {},
cancelSearchPossibleConversations: () => {},
fetchingParticipants: false,
}
},
Expand Down Expand Up @@ -143,6 +144,9 @@ export default {
EventBus.$off('routeChange', this.onRouteChange)
EventBus.$off('joinedConversation', this.onJoinedConversation)
EventBus.$off('Signaling::participantListChanged', this.debounceUpdateParticipants)

this.cancelSearchPossibleConversations()
this.cancelSearchPossibleConversations = null
},

methods: {
Expand All @@ -152,7 +156,7 @@ export default {
onRouteChange() {
// Reset participantsInitialised when there is only the current user in the participant list
this.participantsInitialised = this.$store.getters.participantsList(this.token).length > 1
this.searchText = ''
this.abortSearch()
},

/**
Expand Down Expand Up @@ -187,10 +191,21 @@ export default {

async fetchSearchResults() {
try {
const response = await searchPossibleConversations(this.searchText, this.token)
this.searchResults = response.data.ocs.data
this.cancelSearchPossibleConversations('canceled')
const { request, cancel } = CancelableRequest(searchPossibleConversations)
this.cancelSearchPossibleConversations = cancel

const response = await request({
searchText: this.searchText,
token: this.token,
})

this.searchResults = response?.data?.ocs?.data || []
this.contactsLoading = false
} catch (exception) {
if (CancelableRequest.isCancel(exception)) {
return
}
console.error(exception)
showError(t('spreed', 'An error occurred while performing the search'))
}
Expand All @@ -205,7 +220,7 @@ export default {
async addParticipants(item) {
try {
await addParticipant(this.token, item.id, item.source)
this.searchText = ''
this.abortSearch()
this.cancelableGetParticipants()
} catch (exception) {
console.debug(exception)
Expand Down Expand Up @@ -268,6 +283,9 @@ export default {
// Ends the search operation
abortSearch() {
this.searchText = ''
if (this.cancelSearchPossibleConversations) {
this.cancelSearchPossibleConversations()
}
},
},
}
Expand Down
37 changes: 14 additions & 23 deletions src/services/conversationsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,29 +91,24 @@ const checkTalkVersionHash = function(response) {
/**
* Fetch listed conversations
* @param {string} searchTerm The string that will be used in the search query.
* @param {object} options options
*/
const searchListedConversations = async function(searchTerm) {
const response = await axios.get(generateOcsUrl('apps/spreed/api/v3', 2) + 'listed-room', {
const searchListedConversations = async function(searchTerm, options) {
return axios.get(generateOcsUrl('apps/spreed/api/v3', 2) + 'listed-room', Object.assign(options, {
params: {
searchTerm,
},
})

if (maintenanceWarning) {
maintenanceWarning.hideToast()
maintenanceWarning = null
}

return response
}))
}

/**
* Fetch possible conversations
* @param {string} searchText The string that will be used in the search query.
* @param {object} options options
* @param {string} [token] The token of the conversation (if any)
* @param {boolean} [onlyUsers] Only return users
*/
const searchPossibleConversations = async function(searchText, token, onlyUsers) {
const searchPossibleConversations = async function({ searchText, token, onlyUsers }, options) {
token = token || 'new'
onlyUsers = !!onlyUsers
const shareTypes = [
Expand All @@ -128,18 +123,14 @@ const searchPossibleConversations = async function(searchText, token, onlyUsers)
}
}

try {
return await axios.get(generateOcsUrl('core/autocomplete', 2) + `get`, {
params: {
search: searchText,
itemType: 'call',
itemId: token,
shareTypes,
},
})
} catch (error) {
console.debug('Error while searching possible conversations: ', error)
}
return axios.get(generateOcsUrl('core/autocomplete', 2) + `get`, Object.assign(options, {
params: {
search: searchText,
itemType: 'call',
itemId: token,
shareTypes,
},
}))
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/utils/cancelableRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,7 @@ const CancelableRequest = function(request) {
}
}

// expose function to check if an exception is from a cancellation
CancelableRequest.isCancel = axios.isCancel

export default CancelableRequest

0 comments on commit 235f5d9

Please sign in to comment.