From 92ffc4115bcbe9b0bcf43edc5bbf73c702556e72 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 00:25:24 +0000 Subject: [PATCH] move some codes to data service --- .../src/lib/challenge-search-data.service.ts | 131 +++++++++++ .../src/lib/challenge-search.component.ts | 214 +++--------------- 2 files changed, 162 insertions(+), 183 deletions(-) create mode 100644 libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts new file mode 100644 index 0000000000..be07285065 --- /dev/null +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, forkJoin, iif, Observable, of, Subject } from 'rxjs'; +import { + catchError, + debounceTime, + distinctUntilChanged, + map, + switchMap, + takeUntil, +} from 'rxjs/operators'; +import { + ChallengePlatformSearchQuery, + ChallengePlatformService, + ChallengePlatformSort, + Image, + ImageAspectRatio, + ImageHeight, + ImageQuery, + ImageService, + Organization, + OrganizationSearchQuery, + OrganizationService, + OrganizationSort, +} from '@sagebionetworks/openchallenges/api-client-angular'; +import { forkJoinConcurrent } from '@sagebionetworks/openchallenges/util'; +import { Filter } from '@sagebionetworks/openchallenges/ui'; + +@Injectable({ + providedIn: 'root', +}) +export class ChallengeSearchDataService { + private platformSearchTerms: BehaviorSubject = + new BehaviorSubject(''); + + private organizationSearchTerms: BehaviorSubject = + new BehaviorSubject(''); + + private destroy = new Subject(); + + constructor( + private challengePlatformService: ChallengePlatformService, + private organizationService: OrganizationService, + private imageService: ImageService + ) {} + + setPlatformSearchTerms(searchTerms: string) { + this.platformSearchTerms.next(searchTerms); + } + + setOriganizationSearchTerms(searchTerms: string) { + this.organizationSearchTerms.next(searchTerms); + } + + searchPlatforms(): Observable { + return this.platformSearchTerms.pipe( + debounceTime(400), + distinctUntilChanged(), + takeUntil(this.destroy), + switchMap((searchTerm: string) => { + const sortedBy: ChallengePlatformSort = 'name'; + const platformQuery: ChallengePlatformSearchQuery = { + searchTerms: searchTerm, + sort: sortedBy, + }; + return this.challengePlatformService.listChallengePlatforms( + platformQuery + ); + }), + map((page) => + page.challengePlatforms.map((platform) => ({ + value: platform.slug, + label: platform.name, + active: false, + })) + ) + ); + } + + searchOriganizations(): Observable { + return this.organizationSearchTerms.pipe( + debounceTime(400), + distinctUntilChanged(), + // takeUntil(this.destroy), + switchMap((searchTerm: string) => { + const sortBy: OrganizationSort = 'name'; + const orgQuery: OrganizationSearchQuery = { + searchTerms: searchTerm, + sort: sortBy, + }; + return this.organizationService.listOrganizations(orgQuery); + }), + map((page) => page.organizations), + switchMap((orgs) => + forkJoin({ + orgs: of(orgs), + avatarUrls: forkJoinConcurrent( + orgs.map((org) => this.getOrganizationAvatarUrl(org)), + Infinity + ), + }) + ), + map(({ orgs, avatarUrls }) => + orgs.map((org, index) => ({ + value: org.id, + label: org.name, + avatarUrl: avatarUrls[index]?.url, + active: false, + })) + ) + ); + } + + private getOrganizationAvatarUrl(org: Organization): Observable { + return iif( + () => !!org.avatarKey, + this.imageService.getImage({ + objectKey: org.avatarKey, + height: ImageHeight._32px, + aspectRatio: ImageAspectRatio._11, + } as ImageQuery), + of({ url: '' }) + ).pipe( + catchError(() => { + console.error( + 'Unable to get the image url. Please check the logs of the image service.' + ); + return of({ url: '' }); + }) + ); + } +} diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts index feb4a25661..2653d622a4 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -10,25 +10,10 @@ import { Challenge, ChallengeService, ChallengeSearchQuery, - ChallengePlatformService, - ChallengePlatformSearchQuery, - ChallengeInputDataTypeService, - ChallengeInputDataTypeSearchQuery, - ImageService, - OrganizationService, - OrganizationSearchQuery, - Organization, - Image, - ImageQuery, - ImageHeight, - ImageAspectRatio, ChallengeSort, ChallengeStatus, ChallengeSubmissionType, ChallengeIncentive, - ChallengePlatformSort, - ChallengeInputDataTypeSort, - OrganizationSort, ChallengeCategory, } from '@sagebionetworks/openchallenges/api-client-angular'; import { ConfigService } from '@sagebionetworks/openchallenges/config'; @@ -53,17 +38,7 @@ import { challengeCategoriesFilterPanel, } from './challenge-search-filter-panels'; import { challengeSortFilter } from './challenge-search-filters'; -import { - BehaviorSubject, - Observable, - Subject, - forkJoin, - iif, - map, - of, - switchMap, - throwError, -} from 'rxjs'; +import { BehaviorSubject, Subject, switchMap, throwError } from 'rxjs'; import { catchError, debounceTime, @@ -77,8 +52,7 @@ import { CommonModule, DatePipe, Location } from '@angular/common'; import { assign, union } from 'lodash'; import { DateRange } from './date-range'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import { forkJoinConcurrent } from '@sagebionetworks/openchallenges/util'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { DividerModule } from 'primeng/divider'; @@ -89,6 +63,7 @@ import { RadioButtonModule } from 'primeng/radiobutton'; import { SeoService } from '@sagebionetworks/shared/util'; import { getSeoData } from './challenge-search-seo-data'; import { HttpParams } from '@angular/common/http'; +import { ChallengeSearchDataService } from './challenge-search-data.service'; @Component({ selector: 'openchallenges-challenge-search', @@ -128,29 +103,9 @@ export class ChallengeSearchComponent private query: BehaviorSubject = new BehaviorSubject({}); - // set a default behaviorSubject to trigger searchTearm's changes - private allSearchTerms = new BehaviorSubject<{ - challenge: string; - platform: string; - organization: string; - }>({ - challenge: '', - platform: '', - organization: '', - }); - private challengeSearchTerms: BehaviorSubject = new BehaviorSubject(''); - private platformSearchTerms: BehaviorSubject = - new BehaviorSubject(''); - - private inputDataTypeSearchTerms: BehaviorSubject = - new BehaviorSubject(''); - - private organizationSearchTerms: BehaviorSubject = - new BehaviorSubject(''); - private destroy = new Subject(); challenges: Challenge[] = []; @@ -202,12 +157,8 @@ export class ChallengeSearchComponent constructor( private activatedRoute: ActivatedRoute, - private router: Router, private challengeService: ChallengeService, - private challengePlatformService: ChallengePlatformService, - private challengeInputDataTypeService: ChallengeInputDataTypeService, - private organizationService: OrganizationService, - private imageService: ImageService, + private challengeSearchDataService: ChallengeSearchDataService, private readonly configService: ConfigService, private _snackBar: MatSnackBar, private seoService: SeoService, @@ -228,23 +179,15 @@ export class ChallengeSearchComponent this.selectedMinStartDate = params['minStartDate']; this.selectedMaxStartDate = params['maxStartDate']; - const isDateDefined = params['minStartDate'] || params['maxStartDate']; - - if (isDateDefined) { + if (params['minStartDate'] || params['maxStartDate']) { if (this.refreshed) { // display custom range only once with defined date query after refreshing this.selectedYear = 'custom'; this.isCustomYear = true; - const yearRange = [ - params['minStartDate'] - ? new Date(params['minStartDate']) - : undefined, - params['maxStartDate'] - ? new Date(params['maxStartDate']) - : undefined, + this.customMonthRange = [ + new Date(params['minStartDate']), + new Date(params['maxStartDate']), ]; - - this.customMonthRange = yearRange as Date[]; this.refreshed = false; } } else { @@ -262,7 +205,6 @@ export class ChallengeSearchComponent this.selectedOrgs = this.splitParam(params['organizations']).map( (idString) => +idString ); - this.searchedTerms = params['searchTerms']; this.selectedPageNumber = +params['pageNumber'] || this.defaultPageNumber; this.selectedPageSize = +params['pageSize'] || this.defaultPageSize; @@ -293,114 +235,39 @@ export class ChallengeSearchComponent .subscribe((page) => (this.totalChallengesCount = page.totalElements)); // update platform filter values - this.platformSearchTerms - .pipe( - debounceTime(400), - distinctUntilChanged(), - takeUntil(this.destroy), - switchMap((searchTerm: string) => { - const sortedBy: ChallengePlatformSort = 'name'; - const platformQuery: ChallengePlatformSearchQuery = { - searchTerms: searchTerm, - sort: sortedBy, - }; - return this.challengePlatformService.listChallengePlatforms( - platformQuery - ); - }) - ) - .subscribe((page) => { - const searchedPlatforms = page.challengePlatforms.map((platform) => ({ - value: platform.slug, - label: platform.name, - active: false, - })) as Filter[]; - - const selectedPlatformValues = searchedPlatforms.filter((option) => + this.challengeSearchDataService + .searchPlatforms() + .pipe(takeUntil(this.destroy)) + .subscribe((options) => { + const selectedPlatformValues = options.filter((option) => this.selectedPlatforms.includes(option.value as string) ); - this.platformsFilter.options = union( - searchedPlatforms, - selectedPlatformValues - ) as Filter[]; + this.platformsFilter.options = union(options, selectedPlatformValues); }); // update input data type filter values - this.inputDataTypeSearchTerms - .pipe( - debounceTime(400), - distinctUntilChanged(), - takeUntil(this.destroy), - switchMap((searchTerm: string) => { - const sortedBy: ChallengeInputDataTypeSort = 'name'; - const inputDataTypeQuery: ChallengeInputDataTypeSearchQuery = { - searchTerms: searchTerm, - sort: sortedBy, - }; - return this.challengeInputDataTypeService.listChallengeInputDataTypes( - inputDataTypeQuery - ); - }) - ) - .subscribe((page) => { - const searchedInputDataTypes = page.challengeInputDataTypes.map( - (dataType) => ({ - value: dataType.slug, - label: dataType.name, - active: false, - }) - ) as Filter[]; - - const selectedInputDataTypesValues = searchedInputDataTypes.filter( - (option) => - this.selectedInputDataTypes.includes(option.value as string) + this.challengeSearchDataService + .searchOriganizations() + .pipe(takeUntil(this.destroy)) + .subscribe((options) => { + const selectedInputDataTypesValues = options.filter((option) => + this.selectedInputDataTypes.includes(option.value as string) ); this.inputDataTypesFilter.options = union( - searchedInputDataTypes, + options, selectedInputDataTypesValues - ) as Filter[]; + ); }); // update organization filter values - this.organizationSearchTerms - .pipe( - debounceTime(400), - distinctUntilChanged(), - takeUntil(this.destroy), - switchMap((searchTerm: string) => { - const sortBy: OrganizationSort = 'name'; - const orgQuery: OrganizationSearchQuery = { - searchTerms: searchTerm, - sort: sortBy, - }; - return this.organizationService.listOrganizations(orgQuery); - }), - map((page) => page.organizations), - switchMap((orgs) => - forkJoin({ - orgs: of(orgs), - avatarUrls: forkJoinConcurrent( - orgs.map((org) => this.getOrganizationAvatarUrl(org)), - Infinity - ), - }) - ) - ) - .subscribe(({ orgs, avatarUrls }) => { - const searchedOrgs = orgs.map((org, index) => ({ - value: org.id, - label: org.name, - avatarUrl: avatarUrls[index]?.url, - active: false, - })) as Filter[]; - - const selectedOrgValues = searchedOrgs.filter((option) => + this.challengeSearchDataService + .searchOriganizations() + .pipe(takeUntil(this.destroy)) + .subscribe((options) => { + const selectedOrgValues = options.filter((option) => this.selectedOrgs.includes(option.value as number) ); - this.organizationsFilter.options = union( - searchedOrgs, - selectedOrgValues - ) as Filter[]; + this.organizationsFilter.options = union(options, selectedOrgValues); }); } @@ -413,7 +280,7 @@ export class ChallengeSearchComponent takeUntil(this.destroy) ) .subscribe((searched) => { - this.onParamChange({ organizations: searched }); + this.onParamChange({ searchTerms: searched }); }); this.query @@ -491,10 +358,10 @@ export class ChallengeSearchComponent this.challengeSearchTerms.next(searched); break; case 'platforms': - this.platformSearchTerms.next(searched); + this.challengeSearchDataService.setPlatformSearchTerms(searched); break; case 'organizations': - this.organizationSearchTerms.next(searched); + this.challengeSearchDataService.setOriganizationSearchTerms(searched); break; } } @@ -518,23 +385,4 @@ export class ChallengeSearchComponent duration: 30000, }); } - - private getOrganizationAvatarUrl(org: Organization): Observable { - return iif( - () => !!org.avatarKey, - this.imageService.getImage({ - objectKey: org.avatarKey, - height: ImageHeight._32px, - aspectRatio: ImageAspectRatio._11, - } as ImageQuery), - of({ url: '' }) - ).pipe( - catchError(() => { - console.error( - 'Unable to get the image url. Please check the logs of the image service.' - ); - return of({ url: '' }); - }) - ); - } }