diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html index 9d9fb1bcf..63fcc9497 100644 --- a/src/addons/mod/forum/components/index/index.html +++ b/src/addons/mod/forum/components/index/index.html @@ -1,5 +1,8 @@ + + + diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 3283edc33..eb52118e8 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -57,6 +57,8 @@ import { AddonModForumDiscussionItem, AddonModForumDiscussionsSource } from '../ import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CorePromisedValue } from '@classes/promised-value'; +import { CoreNavigator } from '@services/navigator'; +import { FORUM_SEARCH_PAGE_NAME } from '@addons/mod/forum/forum.module'; /** * Component that displays a forum entry page. @@ -332,6 +334,22 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.discussions?.destroy(); } + /** + * Open search page. + */ + async openSearch(): Promise { + if (!this.forum) { + return; + } + + await CoreNavigator.navigateToSitePath(FORUM_SEARCH_PAGE_NAME, { + params: { + courseId: this.courseId, + forumId: this.forum.id, + }, + }); + } + /** * @inheritdoc */ diff --git a/src/addons/mod/forum/pages/search/search.html b/src/addons/mod/forum/pages/search/search.html index 759c8b5b5..24274aa3c 100644 --- a/src/addons/mod/forum/pages/search/search.html +++ b/src/addons/mod/forum/pages/search/search.html @@ -4,11 +4,9 @@ -

{{ 'addon.block_searchforums.pluginname' | translate }}

+

{{ forum.name }}

+

{{ 'addon.block_searchforums.pluginname' | translate }}

- - - diff --git a/src/addons/mod/forum/pages/search/search.ts b/src/addons/mod/forum/pages/search/search.ts index 388ea1a8b..9fa207c70 100644 --- a/src/addons/mod/forum/pages/search/search.ts +++ b/src/addons/mod/forum/pages/search/search.ts @@ -12,18 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AddonModForum, AddonModForumData } from '@addons/mod/forum/services/forum'; import { Component, OnInit } from '@angular/core'; +import { CorePromisedValue } from '@classes/promised-value'; import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; +import { CoreCourse } from '@features/course/services/course'; import { CoreSearchGlobalSearchResultsSource } from '@features/search/classes/global-search-results-source'; import { CoreSearchGlobalSearch, CoreSearchGlobalSearchFilters, CoreSearchGlobalSearchResult, } from '@features/search/services/global-search'; +import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; @Component({ selector: 'page-addon-mod-forum-search', @@ -35,16 +41,20 @@ export class AddonModForumSearchPage implements OnInit { loadMoreError: string | null = null; searchBanner: string | null = null; resultsSource = new CoreSearchGlobalSearchResultsSource('', {}); + forum?: AddonModForumData; searchAreaId?: string; + private ready = new CorePromisedValue(); + /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { try { const site = CoreSites.getRequiredCurrentSite(); const searchBanner = site.config?.searchbanner?.trim() ?? ''; const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const forumId = CoreNavigator.getRouteNumberParam('forumId'); const filters: CoreSearchGlobalSearchFilters = { courseIds: [courseId], }; @@ -53,10 +63,24 @@ export class AddonModForumSearchPage implements OnInit { this.searchBanner = searchBanner; } - filters.searchAreaIds = ['mod_forum-activity', 'mod_forum-post']; - this.searchAreaId = `AddonModForumSearch-${courseId}`; + if (forumId) { + this.forum = await AddonModForum.getForumById(courseId, forumId); + const module = await CoreCourse.getModule(this.forum.cmid, courseId); + + filters.searchAreaIds = ['mod_forum-post']; + + if (module.contextid) { + filters.contextIds = [module.contextid]; + } + + this.searchAreaId = `AddonModForumSearch-${courseId}-${this.forum.id}`; + } else { + filters.searchAreaIds = ['mod_forum-activity', 'mod_forum-post']; + this.searchAreaId = `AddonModForumSearch-${courseId}`; + } this.resultsSource.setFilters(filters); + this.ready.resolve(); } catch (error) { CoreDomUtils.showErrorModal(error); CoreNavigator.back(); @@ -71,6 +95,8 @@ export class AddonModForumSearchPage implements OnInit { * @param query Search query. */ async search(query: string): Promise { + await this.ready; + this.resultsSource.setQuery(query); if (this.resultsSource.hasEmptyQuery()) { @@ -82,6 +108,19 @@ export class AddonModForumSearchPage implements OnInit { await CoreUtils.ignoreErrors( CoreSearchGlobalSearch.logViewResults(this.resultsSource.getQuery(), this.resultsSource.getFilters()), ); + + CoreAnalytics.logEvent({ + type: CoreAnalyticsEventType.VIEW_ITEM_LIST, + ws: 'core_search_view_results', + name: Translate.instant('core.search.globalsearch'), + data: { + query, + filters: JSON.stringify(this.resultsSource.getFilters()), + }, + url: CoreUrlUtils.addParamsToUrl('/search/index.php', { + q: query, + }), + }); }); } @@ -90,6 +129,9 @@ export class AddonModForumSearchPage implements OnInit { */ clearSearch(): void { this.loadMoreError = null; + + this.resultsSource.setQuery(''); + this.resultsSource.reset(); } /** diff --git a/src/addons/mod/forum/tests/behat/search.feature b/src/addons/mod/forum/tests/behat/search.feature index 4fccc6a5e..f9cde8dc2 100644 --- a/src/addons/mod/forum/tests/behat/search.feature +++ b/src/addons/mod/forum/tests/behat/search.feature @@ -28,6 +28,8 @@ Feature: Test Forum Search | forum2 | Initial discussion 2 | Initial discussion 2 | Initial discussion message 2 | | forum3 | Initial discussion 3 | Initial discussion 3 | Initial discussion message 3 | + # TODO test single forum search (lacking generators for post search results) + Scenario: Search in side block Given global search expects the query "message" and will return: | type | idnumber | diff --git a/src/core/features/search/components/global-search-result/global-search-result.ts b/src/core/features/search/components/global-search-result/global-search-result.ts index 437dc3b72..7e60f15e6 100644 --- a/src/core/features/search/components/global-search-result/global-search-result.ts +++ b/src/core/features/search/components/global-search-result/global-search-result.ts @@ -44,7 +44,7 @@ export class CoreSearchGlobalSearchResultComponent implements OnChanges { * @returns Rendered context. */ private computeRenderedContext(): CoreSearchGlobalSearchResultContext | null { - const context = this.result.context ?? {}; + const context = { ...this.result.context } ?? {}; if (this.showCourse === false) { delete context.courseName; diff --git a/src/core/features/search/services/global-search.ts b/src/core/features/search/services/global-search.ts index ecbf8b612..b7728e195 100644 --- a/src/core/features/search/services/global-search.ts +++ b/src/core/features/search/services/global-search.ts @@ -80,6 +80,7 @@ export interface CoreSearchGlobalSearchFilters { searchAreaCategoryIds?: string[]; searchAreaIds?: string[]; courseIds?: number[]; + contextIds?: number[]; } /** @@ -130,7 +131,7 @@ export class CoreSearchGlobalSearchService { const params: CoreSearchGetResultsWSParams = { query, page, - filters: await this.prepareWSFilters(filters), + filters: await this.prepareAdvancedWSFilters(filters), }; const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK); @@ -158,7 +159,7 @@ export class CoreSearchGlobalSearchService { const site = CoreSites.getRequiredCurrentSite(); const params: CoreSearchGetTopResultsWSParams = { query, - filters: await this.prepareWSFilters(filters), + filters: await this.prepareAdvancedWSFilters(filters), }; const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK); @@ -210,7 +211,7 @@ export class CoreSearchGlobalSearchService { const site = CoreSites.getRequiredCurrentSite(); const params: CoreSearchViewResultsWSParams = { query, - filters: await this.prepareWSFilters(filters), + filters: await this.prepareBasicWSFilters(filters), }; await site.write('core_search_view_results', params); @@ -273,17 +274,18 @@ export class CoreSearchGlobalSearchService { */ protected filtersYieldEmptyResults(filters: CoreSearchGlobalSearchFilters): boolean { return filters.courseIds?.length === 0 + || filters.contextIds?.length === 0 || filters.searchAreaIds?.length === 0 || filters.searchAreaCategoryIds?.length === 0; } /** - * Prepare search filters before sending to WS. + * Prepare basic search filters before sending to WS. * * @param filters App filters. - * @returns WS filters. + * @returns Basic WS filters. */ - protected async prepareWSFilters(filters: CoreSearchGlobalSearchFilters): Promise { + protected async prepareBasicWSFilters(filters: CoreSearchGlobalSearchFilters): Promise { const wsFilters: CoreSearchBasicWSFilters = {}; if (filters.courseIds) { @@ -311,6 +313,22 @@ export class CoreSearchGlobalSearchService { return wsFilters; } + /** + * Prepare advanced search filters before sending to WS. + * + * @param filters App filters. + * @returns Advanced WS filters. + */ + protected async prepareAdvancedWSFilters(filters: CoreSearchGlobalSearchFilters): Promise { + const wsFilters: CoreSearchAdvancedWSFilters = await this.prepareBasicWSFilters(filters); + + if (filters.contextIds) { + wsFilters.contextids = filters.contextIds; + } + + return wsFilters; + } + } export const CoreSearchGlobalSearch = makeSingleton(CoreSearchGlobalSearchService);