Merge pull request #53996 from nextcloud/feat/unified_search/online_providers

feat(UnifiedSearch): Online search providers support, toggle option
pull/54463/head
Daniel 2025-08-14 21:08:21 +07:00 committed by GitHub
commit 92e282af0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 92 additions and 6 deletions

@ -87,6 +87,7 @@ namespace OC\Core;
* name: string,
* icon: string,
* order: int,
* isExternalProvider: bool,
* triggers: list<string>,
* filters: array<string, string>,
* inAppSearch: bool,

@ -1045,6 +1045,7 @@
"name",
"icon",
"order",
"isExternalProvider",
"triggers",
"filters",
"inAppSearch"
@ -1066,6 +1067,9 @@
"type": "integer",
"format": "int64"
},
"isExternalProvider": {
"type": "boolean"
},
"triggers": {
"type": "array",
"items": {

@ -1045,6 +1045,7 @@
"name",
"icon",
"order",
"isExternalProvider",
"triggers",
"filters",
"inAppSearch"
@ -1066,6 +1067,9 @@
"type": "integer",
"format": "int64"
},
"isExternalProvider": {
"type": "boolean"
},
"triggers": {
"type": "array",
"items": {

@ -86,6 +86,13 @@
<IconFilter :size="20" />
</template>
</NcButton>
<NcCheckboxRadioSwitch v-if="hasExternalResources"
v-model="searchExternalResources"
type="switch"
class="unified-search-modal__search-external-resources"
:class="{'unified-search-modal__search-external-resources--aligned': localSearch}">
{{ t('core', 'Search connected services') }}
</NcCheckboxRadioSwitch>
</div>
<div class="unified-search-modal__filters-applied">
<FilterChip v-for="filter in filters"
@ -172,6 +179,7 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import NcInputField from '@nextcloud/vue/components/NcInputField'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import CustomDateRangeModal from './CustomDateRangeModal.vue'
import FilterChip from './SearchFilterChip.vue'
@ -198,6 +206,7 @@ export default defineComponent({
NcEmptyContent,
NcDialog,
NcInputField,
NcCheckboxRadioSwitch,
SearchableList,
SearchResult,
},
@ -264,6 +273,7 @@ export default defineComponent({
showDateRangeModal: false,
internalIsVisible: this.open,
initialized: false,
searchExternalResources: false,
}
},
@ -301,6 +311,10 @@ export default defineComponent({
debouncedFilterContacts() {
return debounce(this.filterContacts, 300)
},
hasExternalResources() {
return this.providers.some(provider => provider.isExternalProvider)
},
},
watch: {
@ -338,6 +352,12 @@ export default defineComponent({
this.$emit('update:query', this.searchQuery)
},
},
searchExternalResources() {
if (this.searchQuery) {
this.find(this.searchQuery)
}
},
},
mounted() {
@ -418,6 +438,14 @@ export default defineComponent({
unifiedSearchLogger.debug('Limiting search to', params.limit)
}
const shouldSkipSearch = !this.searchExternalResources && provider.isExternalProvider
const wasManuallySelected = this.filteredProviders.some(filteredProvider => filteredProvider.id === provider.id)
// if the provider is an external resource and the user has not manually selected it, skip the search
if (shouldSkipSearch && !wasManuallySelected) {
this.searching = false
return
}
const request = unifiedSearch(params).request
request().then((response) => {
@ -742,6 +770,21 @@ export default defineComponent({
padding-top: 4px;
}
&__search-external-resources {
:deep(span.checkbox-content) {
padding-top: 0;
padding-bottom: 0;
}
:deep(.checkbox-content__icon) {
margin: auto !important;
}
&--aligned {
margin-inline-start: auto;
}
}
&__filters-applied {
padding-top: 4px;
display: flex;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -739,6 +739,7 @@ return array(
'OCP\\SabrePluginEvent' => $baseDir . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => $baseDir . '/lib/public/SabrePluginException.php',
'OCP\\Search\\FilterDefinition' => $baseDir . '/lib/public/Search/FilterDefinition.php',
'OCP\\Search\\IExternalProvider' => $baseDir . '/lib/public/Search/IExternalProvider.php',
'OCP\\Search\\IFilter' => $baseDir . '/lib/public/Search/IFilter.php',
'OCP\\Search\\IFilterCollection' => $baseDir . '/lib/public/Search/IFilterCollection.php',
'OCP\\Search\\IFilteringProvider' => $baseDir . '/lib/public/Search/IFilteringProvider.php',

@ -780,6 +780,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\SabrePluginEvent' => __DIR__ . '/../../..' . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => __DIR__ . '/../../..' . '/lib/public/SabrePluginException.php',
'OCP\\Search\\FilterDefinition' => __DIR__ . '/../../..' . '/lib/public/Search/FilterDefinition.php',
'OCP\\Search\\IExternalProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IExternalProvider.php',
'OCP\\Search\\IFilter' => __DIR__ . '/../../..' . '/lib/public/Search/IFilter.php',
'OCP\\Search\\IFilterCollection' => __DIR__ . '/../../..' . '/lib/public/Search/IFilterCollection.php',
'OCP\\Search\\IFilteringProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IFilteringProvider.php',

@ -15,6 +15,7 @@ use OCP\IAppConfig;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\FilterDefinition;
use OCP\Search\IExternalProvider;
use OCP\Search\IFilter;
use OCP\Search\IFilteringProvider;
use OCP\Search\IInAppSearch;
@ -178,6 +179,7 @@ class SearchComposer {
if ($order === null) {
return;
}
$isExternalProvider = $provider instanceof IExternalProvider ? $provider->isExternalProvider() : false;
$triggers = [$provider->getId()];
if ($provider instanceof IFilteringProvider) {
$triggers += $provider->getAlternateIds();
@ -192,6 +194,7 @@ class SearchComposer {
'name' => $provider->getName(),
'icon' => $this->fetchIcon($appId, $provider->getId()),
'order' => $order,
'isExternalProvider' => $isExternalProvider,
'triggers' => array_values($triggers),
'filters' => $this->getFiltersType($filters, $provider->getId()),
'inAppSearch' => $provider instanceof IInAppSearch,

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Search;
/**
* Interface for search providers that forward user queries to external services.
*
* @since 32.0.0
*/
interface IExternalProvider extends IProvider {
/**
* Indicates whether this search provider queries external (3rd-party) resources.
* This is used by the Unified Search modal filter (toggle switch). By default, searching through external providers is disabled.
*
* @return bool default false
*
* @since 32.0.0
*/
public function isExternalProvider(): bool;
}

@ -1083,6 +1083,7 @@
"name",
"icon",
"order",
"isExternalProvider",
"triggers",
"filters",
"inAppSearch"
@ -1104,6 +1105,9 @@
"type": "integer",
"format": "int64"
},
"isExternalProvider": {
"type": "boolean"
},
"triggers": {
"type": "array",
"items": {