MOBILE-4207 forum: Implement forum activity search

main
Noel De Martin 2023-07-18 17:04:37 +09:00
parent cd85155953
commit 5873da0e0c
7 changed files with 95 additions and 14 deletions

View File

@ -1,5 +1,8 @@
<!-- Buttons to add to the header. -->
<core-navbar-buttons slot="end">
<ion-button fill="clear" (click)="openSearch()" [attr.aria-label]="'core.search' | translate">
<ion-icon name="fas-magnifying-glass" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
<ion-icon name="fas-circle-info" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -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<void> {
if (!this.forum) {
return;
}
await CoreNavigator.navigateToSitePath(FORUM_SEARCH_PAGE_NAME, {
params: {
courseId: this.courseId,
forumId: this.forum.id,
},
});
}
/**
* @inheritdoc
*/

View File

@ -4,11 +4,9 @@
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
<h1>{{ 'addon.block_searchforums.pluginname' | translate }}</h1>
<h1 *ngIf="forum">{{ forum.name }}</h1>
<h1 *ngIf="!forum">{{ 'addon.block_searchforums.pluginname' | translate }}</h1>
</ion-title>
<ion-buttons slot="end">
<core-user-menu-button></core-user-menu-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="limited-width">

View File

@ -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<void>();
/**
* @inheritdoc
*/
ngOnInit(): void {
async ngOnInit(): Promise<void> {
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<void> {
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();
}
/**

View File

@ -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 |

View File

@ -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;

View File

@ -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<CoreSearchViewResultsWSResponse>('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<CoreSearchBasicWSFilters> {
protected async prepareBasicWSFilters(filters: CoreSearchGlobalSearchFilters): Promise<CoreSearchBasicWSFilters> {
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<CoreSearchAdvancedWSFilters> {
const wsFilters: CoreSearchAdvancedWSFilters = await this.prepareBasicWSFilters(filters);
if (filters.contextIds) {
wsFilters.contextids = filters.contextIds;
}
return wsFilters;
}
}
export const CoreSearchGlobalSearch = makeSingleton(CoreSearchGlobalSearchService);