From e20385aa4431c25a526e358855ff62926e5a3675 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 16:02:21 +0000 Subject: [PATCH 01/17] enable to filter status using location service --- .../src/lib/challenge-search.component.ts | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) 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 3258f9bc92..899e8b747f 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -72,8 +72,8 @@ import { tap, } from 'rxjs/operators'; import { Calendar, CalendarModule } from 'primeng/calendar'; -import { CommonModule, DatePipe } from '@angular/common'; -import { union } from 'lodash'; +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'; @@ -87,6 +87,7 @@ import { PanelModule } from 'primeng/panel'; import { RadioButtonModule } from 'primeng/radiobutton'; import { SeoService } from '@sagebionetworks/shared/util'; import { getSeoData } from './challenge-search-seo-data'; +import { HttpParams } from '@angular/common/http'; @Component({ selector: 'openchallenges-challenge-search', @@ -200,7 +201,8 @@ export class ChallengeSearchComponent private readonly configService: ConfigService, private _snackBar: MatSnackBar, private seoService: SeoService, - private renderer2: Renderer2 + private renderer2: Renderer2, + private _location: Location ) { this.appVersion = this.configService.config.appVersion; this.dataUpdatedOn = this.configService.config.dataUpdatedOn; @@ -488,21 +490,31 @@ export class ChallengeSearchComponent } onStatusChange(selected: string[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - status: this.collapseParam(selected), - }, + const paramName = 'status'; + let params = new HttpParams().delete(paramName); + if (selected.length > 0) { + params = new HttpParams().append( + paramName, + this.collapseParam(selected) ?? '' + ); + } + this._location.replaceState(location.pathname, params.toString()); + const newQuery = assign(this.query.getValue(), { + [paramName]: selected, }); + this.query.next(newQuery); } onSubmissionTypesChange(selected: string[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - submissionTypes: this.collapseParam(selected), - }, - }); + const paramName = 'submissionTypes'; + let params = new HttpParams().delete(paramName); + if (selected.length > 0) { + params = new HttpParams().append( + paramName, + this.collapseParam(selected) ?? '' + ); + } + this._location.replaceState(location.pathname, params.toString()); } onIncentivesChange(selected: string[]): void { From 2117c350a269b0ef7a8d6de65f0add03ec23a9ad Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 16:16:49 +0000 Subject: [PATCH 02/17] refactor checkbox filters --- .../src/lib/challenge-search.component.html | 57 +++++++------- .../src/lib/challenge-search.component.ts | 74 ++++--------------- 2 files changed, 45 insertions(+), 86 deletions(-) diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html index 53b92dd7ad..f3588be719 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html @@ -35,18 +35,18 @@

Challenges

- - - + header="{{ incentivesFilter.label }}" + [toggleable]="true" + [collapsed]="incentivesFilter.collapsed" + > + + + Challenges - - - + header="{{ categoriesFilter.label }}" + [toggleable]="true" + [collapsed]="categoriesFilter.collapsed" + > + + + Challenges placeholder="{{ platformsFilter.label.toLowerCase() + '(s)' }} " [showAvatar]="platformsFilter.showAvatar" [filterByApiClient]="true" - (selectionChange)="onPlatformsChange($event)" - (searchChange)="onPlatformSearchChange($event)" + (selectionChange)="onParamChange('platforms', $event)" + (searchChange)="onSearchChange('platforms', $event)" /> @@ -116,8 +100,8 @@

Challenges

placeholder="{{ organizationsFilter.label.toLowerCase() + '(s)' }} " [showAvatar]="organizationsFilter.showAvatar" [filterByApiClient]="true" - (selectionChange)="onOrganizationsChange($event)" - (searchChange)="onOrganizationSearchChange($event)" + (selectionChange)="onParamChange('organizations', $event)" + (searchChange)="onSearchChange('organizations', $event)" /> 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 28c92f47ef..495789e84a 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -128,9 +128,18 @@ export class ChallengeSearchComponent new BehaviorSubject({}); // set a default behaviorSubject to trigger searchTearm's changes - private searchTerms: BehaviorSubject = new BehaviorSubject( - '' - ); + private allSearchTerms = new BehaviorSubject<{ + challenge: string; + platform: string; + organization: string; + }>({ + challenge: '', + platform: '', + organization: '', + }); + + private challengeSearchTerms: BehaviorSubject = + new BehaviorSubject(''); private platformSearchTerms: BehaviorSubject = new BehaviorSubject(''); @@ -394,7 +403,7 @@ export class ChallengeSearchComponent } ngAfterContentInit(): void { - this.searchTerms + this.challengeSearchTerms .pipe( skip(1), debounceTime(400), @@ -433,10 +442,6 @@ export class ChallengeSearchComponent this.destroy.complete(); } - onSearchChange(): void { - this.searchTerms.next(this.searchedTerms); - } - onYearChange(): void { this.refreshed = false; @@ -475,32 +480,6 @@ export class ChallengeSearchComponent } } - onPlatformsChange(selected: string[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - platforms: this.collapseParam(selected), - }, - }); - } - - onPlatformSearchChange(searched: string): void { - this.platformSearchTerms.next(searched); - } - - onOrganizationsChange(selected: number[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - organizations: this.collapseParam(selected), - }, - }); - } - - onOrganizationSearchChange(searched: string): void { - this.organizationSearchTerms.next(searched); - } - onSortChange(): void { this.router.navigate([], { queryParamsHandling: 'merge', @@ -535,6 +514,23 @@ export class ChallengeSearchComponent this.query.next(newQuery); } + onSearchChange( + searchType: 'challenges' | 'platforms' | 'organizations', + searched: string + ): void { + switch (searchType) { + case 'challenges': + this.challengeSearchTerms.next(searched); + break; + case 'platforms': + this.platformSearchTerms.next(searched); + break; + case 'organizations': + this.organizationSearchTerms.next(searched); + break; + } + } + splitParam(activeParam: string | undefined, by = ','): any[] { return activeParam ? activeParam.split(by) : []; } From 08d39205aefaba9e52849c01f9d255699ccb31b3 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 18:44:09 +0000 Subject: [PATCH 05/17] enable sorter --- .../challenge-search/src/lib/challenge-search.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html index fbc276e373..1012eab2ba 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html @@ -27,7 +27,7 @@

Challenges

optionLabel="label" optionValue="value" scrollHeight="unset" - (onChange)="onSortChange()" + (onChange)="onParamChange('sort', sortedBy)" >
From 07f479e9eac9c15ae73e1a273b245cfff41354b6 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 22:35:23 +0000 Subject: [PATCH 06/17] refactor filter types in preparation of accepting multiple params change at the same time --- .../src/lib/challenge-search-filter-panels.ts | 80 ++++++ .../lib/challenge-search-filters-values.ts | 163 ------------- .../src/lib/challenge-search-filters.ts | 227 ++++++++++++------ .../src/lib/challenge-search.component.html | 40 +-- .../src/lib/challenge-search.component.ts | 97 ++++---- .../src/lib/org-search-filter-panels.ts | 19 ++ .../src/lib/org-search-filters-values.ts | 34 --- .../org-search/src/lib/org-search-filters.ts | 47 ++-- .../src/lib/org-search.component.html | 4 +- .../src/lib/org-search.component.ts | 16 +- libs/openchallenges/ui/src/index.ts | 2 +- .../checkbox-filter.component.html | 14 +- .../checkbox-filter.component.ts | 6 +- .../lib/checkbox-filter/filter-panel.model.ts | 14 ++ .../lib/checkbox-filter/filter-value.model.ts | 10 - .../src/lib/checkbox-filter/filter.model.ts | 20 +- .../search-dropdown-filter.component.html | 12 +- .../search-dropdown-filter.component.ts | 14 +- 18 files changed, 406 insertions(+), 413 deletions(-) create mode 100644 libs/openchallenges/challenge-search/src/lib/challenge-search-filter-panels.ts delete mode 100644 libs/openchallenges/challenge-search/src/lib/challenge-search-filters-values.ts create mode 100644 libs/openchallenges/org-search/src/lib/org-search-filter-panels.ts delete mode 100644 libs/openchallenges/org-search/src/lib/org-search-filters-values.ts create mode 100644 libs/openchallenges/ui/src/lib/checkbox-filter/filter-panel.model.ts delete mode 100644 libs/openchallenges/ui/src/lib/checkbox-filter/filter-value.model.ts diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search-filter-panels.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search-filter-panels.ts new file mode 100644 index 0000000000..bc0139893a --- /dev/null +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search-filter-panels.ts @@ -0,0 +1,80 @@ +import { FilterPanel } from '@sagebionetworks/openchallenges/ui'; +import { + challengeStartYearRangeFilter, + challengeStatusFilter, + challengeInputDataTypesFilter, + challengeSubmissionTypesFilter, + challengeIncentivesFilter, + challengePlatformsFilter, + challengeOrganizationsFilter, + challengeOrganizersFilter, + challengeCategoriesFilter, +} from './challenge-search-filters'; + +export const challengeStartYearRangeFilterPanel: FilterPanel = { + query: 'startYearRange', + label: 'Challenge Year', + options: challengeStartYearRangeFilter, + collapsed: false, +}; + +// checkbox filters +export const challengeStatusFilterPanel: FilterPanel = { + query: 'status', + label: 'Status', + options: challengeStatusFilter, + collapsed: false, +}; + +export const challengeSubmissionTypesFilterPanel: FilterPanel = { + query: 'submissionTypes', + label: 'Submission Type', + options: challengeSubmissionTypesFilter, + collapsed: false, +}; + +export const challengeIncentivesFilterPanel: FilterPanel = { + query: 'incentives', + label: 'Incentive Type', + options: challengeIncentivesFilter, + collapsed: false, +}; + +export const challengePlatformsFilterPanel: FilterPanel = { + query: 'platforms', + label: 'Platform', + options: challengePlatformsFilter, + collapsed: false, +}; + +// dropdown filters +export const challengeInputDataTypesFilterPanel: FilterPanel = { + query: 'inputDataTypes', + label: 'Input Data Type', + options: challengeInputDataTypesFilter, + collapsed: false, + showAvatar: false, +}; + +export const challengeCategoriesFilterPanel: FilterPanel = { + query: 'categories', + label: 'Category', + options: challengeCategoriesFilter, + collapsed: false, +}; + +export const challengeOrganizationsFilterPanel: FilterPanel = { + query: 'organizations', + label: 'Organization', + options: challengeOrganizationsFilter, + collapsed: false, + showAvatar: true, +}; + +export const challengeOrganizatersFilterPanel: FilterPanel = { + query: 'organizers', + label: 'Organizer', + options: challengeOrganizersFilter, + collapsed: false, + showAvatar: true, +}; diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters-values.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search-filters-values.ts deleted file mode 100644 index c7aaaf7639..0000000000 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters-values.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { FilterValue } from '@sagebionetworks/openchallenges/ui'; - -const thisYear = new Date().getFullYear(); - -const updateYear = ( - thisYear: number, - startYearDiff: number, - endYearDiff: number -) => { - return { - start: `${thisYear + startYearDiff}-01-01`, - end: `${thisYear + endYearDiff}-12-31`, - }; -}; - -export const challengeStartYearRangeFilterValues: FilterValue[] = [ - { - value: undefined, - label: 'All', - }, - // { - // value: updateYear(thisYear, 1, 1), - // label: (thisYear + 1).toString(), - // }, - { - value: updateYear(thisYear, 0, 0), - label: thisYear.toString(), - }, - { - value: updateYear(thisYear, -1, -1), - label: (thisYear - 1).toString(), - }, - { - value: updateYear(thisYear, -6, -2), - label: thisYear - 6 + ' - ' + (thisYear - 2), - }, - { - value: updateYear(thisYear, -11, -7), - label: thisYear - 11 + ' - ' + (thisYear - 7), - }, - { - value: updateYear(thisYear, -21, -12), - label: thisYear - 21 + ' - ' + (thisYear - 12), - }, - { - value: 'custom', - label: 'Custom', - }, -]; - -export const challengeStatusFilterValues: FilterValue[] = [ - { - value: 'active', - label: 'Active', - }, - { - value: 'upcoming', - label: 'Upcoming', - }, - { - value: 'completed', - label: 'Completed', - }, -]; - -export const challengeDifficultyFilterValues: FilterValue[] = [ - { - value: 'good_for_beginners', - label: 'Good For Beginners', - }, - { - value: 'intermediate', - label: 'Intermediate', - }, - { - value: 'advanced', - label: 'Advanced', - }, -]; - -export const challengeSubmissionTypesFilterValues: FilterValue[] = [ - { - value: 'container_image', - label: 'Container Image', - }, - { - value: 'prediction_file', - label: 'Prediction File', - }, - { - value: 'notebook', - label: 'Notebook', - }, - { - value: 'other', - label: 'Other', - }, -]; - -export const challengeIncentivesFilterValues: FilterValue[] = [ - { - value: 'monetary', - label: 'Monetary', - }, - { - value: 'publication', - label: 'Publication', - }, - { - value: 'speaking_engagement', - label: 'Speaking Engagement', - }, - { - value: 'other', - label: 'Other', - }, -]; - -export const challengePlatformsFilterValues: FilterValue[] = []; - -export const challengeInputDataTypesFilterValues: FilterValue[] = []; - -export const challengeCategoriesFilterValues: FilterValue[] = [ - { - value: 'featured', - label: 'Featured', - }, - { - value: 'starting_soon', - label: 'Starting Soon', - }, - { - value: 'ending_soon', - label: 'Closing Soon', - }, - { - value: 'recently_started', - label: 'Recently Launched', - }, - { - value: 'recently_ended', - label: 'Recently Completed', - }, -]; - -export const challengeOrganizationsFilterValues: FilterValue[] = []; - -export const challengeOrganizersFilterValues: FilterValue[] = []; - -export const challengeSortFilterValues: FilterValue[] = [ - { - value: 'relevance', - label: 'Relevance', - }, - { - value: 'start_date', - label: 'Start Date', - }, - { - value: 'starred', - label: 'Most Starred', - }, -]; diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts index b79c7d8546..04a6a27b3b 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts @@ -1,88 +1,163 @@ import { Filter } from '@sagebionetworks/openchallenges/ui'; -import { - challengeStartYearRangeFilterValues, - challengeStatusFilterValues, - // challengeDifficultyFilterValues, - challengeInputDataTypesFilterValues, - challengeSubmissionTypesFilterValues, - challengeIncentivesFilterValues, - challengePlatformsFilterValues, - challengeOrganizationsFilterValues, - challengeOrganizersFilterValues, - challengeCategoriesFilterValues, -} from './challenge-search-filters-values'; -export const challengeStartYearRangeFilter: Filter = { - query: 'startYearRange', - label: 'Challenge Year', - values: challengeStartYearRangeFilterValues, - collapsed: false, -}; +const thisYear = new Date().getFullYear(); -// checkbox filters -export const challengeStatusFilter: Filter = { - query: 'status', - label: 'Status', - values: challengeStatusFilterValues, - collapsed: false, +const updateYear = ( + thisYear: number, + startYearDiff: number, + endYearDiff: number +) => { + return { + start: `${thisYear + startYearDiff}-01-01`, + end: `${thisYear + endYearDiff}-12-31`, + }; }; -// export const challengeDifficultyFilter: Filter = { -// query: 'difficulties', -// label: 'Difficulty', -// values: challengeDifficultyFilterValues, -// collapsed: true, -// }; +export const challengeStartYearRangeFilter: Filter[] = [ + { + value: undefined, + label: 'All', + }, + // { + // value: updateYear(thisYear, 1, 1), + // label: (thisYear + 1).toString(), + // }, + { + value: updateYear(thisYear, 0, 0), + label: thisYear.toString(), + }, + { + value: updateYear(thisYear, -1, -1), + label: (thisYear - 1).toString(), + }, + { + value: updateYear(thisYear, -6, -2), + label: thisYear - 6 + ' - ' + (thisYear - 2), + }, + { + value: updateYear(thisYear, -11, -7), + label: thisYear - 11 + ' - ' + (thisYear - 7), + }, + { + value: updateYear(thisYear, -21, -12), + label: thisYear - 21 + ' - ' + (thisYear - 12), + }, + { + value: 'custom', + label: 'Custom', + }, +]; -export const challengeSubmissionTypesFilter: Filter = { - query: 'submissionTypes', - label: 'Submission Type', - values: challengeSubmissionTypesFilterValues, - collapsed: false, -}; +export const challengeStatusFilter: Filter[] = [ + { + value: 'active', + label: 'Active', + }, + { + value: 'upcoming', + label: 'Upcoming', + }, + { + value: 'completed', + label: 'Completed', + }, +]; -export const challengeIncentivesFilter: Filter = { - query: 'incentives', - label: 'Incentive Type', - values: challengeIncentivesFilterValues, - collapsed: false, -}; +export const challengeDifficultyFilter: Filter[] = [ + { + value: 'good_for_beginners', + label: 'Good For Beginners', + }, + { + value: 'intermediate', + label: 'Intermediate', + }, + { + value: 'advanced', + label: 'Advanced', + }, +]; -export const challengePlatformsFilter: Filter = { - query: 'platforms', - label: 'Platform', - values: challengePlatformsFilterValues, - collapsed: false, -}; +export const challengeSubmissionTypesFilter: Filter[] = [ + { + value: 'container_image', + label: 'Container Image', + }, + { + value: 'prediction_file', + label: 'Prediction File', + }, + { + value: 'notebook', + label: 'Notebook', + }, + { + value: 'other', + label: 'Other', + }, +]; -// dropdown filters -export const challengeInputDataTypesFilter: Filter = { - query: 'inputDataTypes', - label: 'Input Data Type', - values: challengeInputDataTypesFilterValues, - collapsed: false, - showAvatar: false, -}; +export const challengeIncentivesFilter: Filter[] = [ + { + value: 'monetary', + label: 'Monetary', + }, + { + value: 'publication', + label: 'Publication', + }, + { + value: 'speaking_engagement', + label: 'Speaking Engagement', + }, + { + value: 'other', + label: 'Other', + }, +]; -export const challengeCategoriesFilter: Filter = { - query: 'categories', - label: 'Category', - values: challengeCategoriesFilterValues, - collapsed: false, -}; +export const challengePlatformsFilter: Filter[] = []; -export const challengeOrganizationsFilter: Filter = { - query: 'organizations', - label: 'Organization', - values: challengeOrganizationsFilterValues, - collapsed: false, - showAvatar: true, -}; +export const challengeInputDataTypesFilter: Filter[] = []; -export const challengeOrganizatersFilter: Filter = { - query: 'organizers', - label: 'Organizer', - values: challengeOrganizersFilterValues, - collapsed: false, - showAvatar: true, -}; +export const challengeCategoriesFilter: Filter[] = [ + { + value: 'featured', + label: 'Featured', + }, + { + value: 'starting_soon', + label: 'Starting Soon', + }, + { + value: 'ending_soon', + label: 'Closing Soon', + }, + { + value: 'recently_started', + label: 'Recently Launched', + }, + { + value: 'recently_ended', + label: 'Recently Completed', + }, +]; + +export const challengeOrganizationsFilter: Filter[] = []; + +export const challengeOrganizersFilter: Filter[] = []; + +export const challengeSortFilter: Filter[] = [ + { + value: 'relevance', + label: 'Relevance', + }, + { + value: 'start_date', + label: 'Start Date', + }, + { + value: 'starred', + label: 'Most Starred', + }, +]; diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html index 1012eab2ba..3490780126 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html @@ -27,7 +27,7 @@

Challenges

optionLabel="label" optionValue="value" scrollHeight="unset" - (onChange)="onParamChange('sort', sortedBy)" + (onChange)="onParamChange({ sort: sortedBy })" > @@ -40,9 +40,9 @@

Challenges

[collapsed]="incentivesFilter.collapsed" > @@ -53,9 +53,9 @@

Challenges

[collapsed]="statusFilter.collapsed" > @@ -66,9 +66,9 @@

Challenges

[collapsed]="categoriesFilter.collapsed" > @@ -79,12 +79,12 @@

Challenges

[collapsed]="platformsFilter.collapsed" > @@ -95,12 +95,12 @@

Challenges

[collapsed]="organizationsFilter.collapsed" > @@ -110,7 +110,7 @@

Challenges

[toggleable]="true" [collapsed]="startYearRangeFilter.collapsed" > -
+
Challenges [collapsed]="submissionTypesFilter.collapsed" > 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 495789e84a..571f2c85b3 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -36,22 +36,23 @@ import { ChallengeCardComponent, CheckboxFilterComponent, Filter, + FilterPanel, FilterValue, FooterComponent, PaginatorComponent, SearchDropdownFilterComponent, } from '@sagebionetworks/openchallenges/ui'; import { - challengeStartYearRangeFilter, - challengeStatusFilter, - challengeSubmissionTypesFilter, - challengeInputDataTypesFilter, - challengeIncentivesFilter, - challengePlatformsFilter, - challengeOrganizationsFilter, - challengeCategoriesFilter, -} from './challenge-search-filters'; -import { challengeSortFilterValues } from './challenge-search-filters-values'; + challengeStartYearRangeFilterPanel, + challengeStatusFilterPanel, + challengeSubmissionTypesFilterPanel, + challengeInputDataTypesFilterPanel, + challengeIncentivesFilterPanel, + challengePlatformsFilterPanel, + challengeOrganizationsFilterPanel, + challengeCategoriesFilterPanel, +} from './challenge-search-filter-panels'; +import { challengeSortFilter } from './challenge-search-filters'; import { BehaviorSubject, Observable, @@ -176,19 +177,19 @@ export class ChallengeSearchComponent defaultPageSize = 24; // define filters - sortFilters: FilterValue[] = challengeSortFilterValues; - startYearRangeFilter: Filter = challengeStartYearRangeFilter; + sortFilters: Filter[] = challengeSortFilter; + startYearRangeFilter: FilterPanel = challengeStartYearRangeFilterPanel; // checkbox filters - statusFilter = challengeStatusFilter; - submissionTypesFilter = challengeSubmissionTypesFilter; - incentivesFilter = challengeIncentivesFilter; - categoriesFilter = challengeCategoriesFilter; + statusFilter = challengeStatusFilterPanel; + submissionTypesFilter = challengeSubmissionTypesFilterPanel; + incentivesFilter = challengeIncentivesFilterPanel; + categoriesFilter = challengeCategoriesFilterPanel; // dropdown filters - platformsFilter = challengePlatformsFilter; - inputDataTypesFilter = challengeInputDataTypesFilter; - organizationsFilter = challengeOrganizationsFilter; + platformsFilter = challengePlatformsFilterPanel; + inputDataTypesFilter = challengeInputDataTypesFilterPanel; + organizationsFilter = challengeOrganizationsFilterPanel; // define selected filter values selectedStatus!: ChallengeStatus[]; @@ -313,15 +314,15 @@ export class ChallengeSearchComponent value: platform.slug, label: platform.name, active: false, - })) as FilterValue[]; + })) as Filter[]; - const selectedPlatformValues = searchedPlatforms.filter((value) => - this.selectedPlatforms.includes(value.value as string) + const selectedPlatformValues = searchedPlatforms.filter((option) => + this.selectedPlatforms.includes(option.value as string) ); - this.platformsFilter.values = union( + this.platformsFilter.options = union( searchedPlatforms, selectedPlatformValues - ) as FilterValue[]; + ) as Filter[]; }); // update input data type filter values @@ -348,15 +349,16 @@ export class ChallengeSearchComponent label: dataType.name, active: false, }) - ) as FilterValue[]; + ) as Filter[]; const selectedInputDataTypesValues = searchedInputDataTypes.filter( - (value) => this.selectedInputDataTypes.includes(value.value as string) + (option) => + this.selectedInputDataTypes.includes(option.value as string) ); - this.inputDataTypesFilter.values = union( + this.inputDataTypesFilter.options = union( searchedInputDataTypes, selectedInputDataTypesValues - ) as FilterValue[]; + ) as Filter[]; }); // update organization filter values @@ -390,15 +392,15 @@ export class ChallengeSearchComponent label: org.name, avatarUrl: avatarUrls[index]?.url, active: false, - })) as FilterValue[]; + })) as Filter[]; - const selectedOrgValues = searchedOrgs.filter((value) => - this.selectedOrgs.includes(value.value as number) + const selectedOrgValues = searchedOrgs.filter((option) => + this.selectedOrgs.includes(option.value as number) ); - this.organizationsFilter.values = union( + this.organizationsFilter.options = union( searchedOrgs, selectedOrgValues - ) as FilterValue[]; + ) as Filter[]; }); } @@ -411,7 +413,7 @@ export class ChallengeSearchComponent takeUntil(this.destroy) ) .subscribe((searched) => { - this.onParamChange('searchTerms', searched); + this.onParamChange({ organizations: searched }); }); this.query @@ -499,18 +501,15 @@ export class ChallengeSearchComponent }); } - onParamChange(paramName: string, selected: string[] | string): void { - let params = new HttpParams().delete(paramName); - if (selected.length > 0) { - params = new HttpParams().append( - paramName, - this.collapseParam(selected) ?? '' - ); - } + onParamChange(filteredQuery: any): void { + // update params of URL + const params = Object.entries(filteredQuery) + .map(([key, value]) => [key, this.collapseParam(value as FilterValue)]) + .reduce((obj, [key, value]) => obj.append(key, value), new HttpParams()); this._location.replaceState(location.pathname, params.toString()); - const newQuery = assign(this.query.getValue(), { - [paramName]: selected, - }); + + // update query to trigger API call + const newQuery = assign(this.query.getValue(), filteredQuery); this.query.next(newQuery); } @@ -535,10 +534,10 @@ export class ChallengeSearchComponent return activeParam ? activeParam.split(by) : []; } - collapseParam(selectedParam: any, by = ','): string | undefined { - return selectedParam.length === 0 - ? undefined - : this.splitParam(selectedParam.toString()).join(by); + collapseParam(selectedParam: FilterValue | FilterValue[], by = ','): string { + return Array.isArray(selectedParam) + ? selectedParam.map((item) => item?.toString()).join(by) + : (selectedParam as string) ?? ''; } openSnackBar(message: string) { diff --git a/libs/openchallenges/org-search/src/lib/org-search-filter-panels.ts b/libs/openchallenges/org-search/src/lib/org-search-filter-panels.ts new file mode 100644 index 0000000000..4829026ffc --- /dev/null +++ b/libs/openchallenges/org-search/src/lib/org-search-filter-panels.ts @@ -0,0 +1,19 @@ +import { FilterPanel } from '@sagebionetworks/openchallenges/ui'; +import { + challengeContributionRolesFilter, + organizationCategoriesFilter, +} from './org-search-filters'; + +export const challengeContributionRolesFilterPanel: FilterPanel = { + query: 'challengeContributionRoles', + label: 'Contribution Role', + options: challengeContributionRolesFilter, + collapsed: false, +}; + +export const organizationCategoriesFilterPanel: FilterPanel = { + query: 'categories', + label: 'Category', + options: organizationCategoriesFilter, + collapsed: false, +}; diff --git a/libs/openchallenges/org-search/src/lib/org-search-filters-values.ts b/libs/openchallenges/org-search/src/lib/org-search-filters-values.ts deleted file mode 100644 index 2844311141..0000000000 --- a/libs/openchallenges/org-search/src/lib/org-search-filters-values.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { FilterValue } from '@sagebionetworks/openchallenges/ui'; - -export const challengeContributionRolesFilterValues: FilterValue[] = [ - { - value: 'challenge_organizer', - label: 'Challenge Organizer', - }, - { - value: 'data_contributor', - label: 'Data Contributor', - }, - { - value: 'sponsor', - label: 'Sponsor', - }, -]; - -export const organizationCategoriesFilterValues: FilterValue[] = [ - { - value: 'featured', - label: 'Featured', - }, -]; - -export const organizationSortFilterValues: FilterValue[] = [ - { - value: 'relevance', - label: 'Relevance', - }, - { - value: 'challenge_count', - label: 'Challenge Count', - }, -]; diff --git a/libs/openchallenges/org-search/src/lib/org-search-filters.ts b/libs/openchallenges/org-search/src/lib/org-search-filters.ts index 85ae07e51d..fed86989c0 100644 --- a/libs/openchallenges/org-search/src/lib/org-search-filters.ts +++ b/libs/openchallenges/org-search/src/lib/org-search-filters.ts @@ -1,19 +1,34 @@ import { Filter } from '@sagebionetworks/openchallenges/ui'; -import { - challengeContributionRolesFilterValues, - organizationCategoriesFilterValues, -} from './org-search-filters-values'; -export const challengeContributionRolesFilter: Filter = { - query: 'challengeContributionRoles', - label: 'Contribution Role', - values: challengeContributionRolesFilterValues, - collapsed: false, -}; +export const challengeContributionRolesFilter: Filter[] = [ + { + value: 'challenge_organizer', + label: 'Challenge Organizer', + }, + { + value: 'data_contributor', + label: 'Data Contributor', + }, + { + value: 'sponsor', + label: 'Sponsor', + }, +]; -export const organizationCategoriesFilter: Filter = { - query: 'categories', - label: 'Category', - values: organizationCategoriesFilterValues, - collapsed: false, -}; +export const organizationCategoriesFilter: Filter[] = [ + { + value: 'featured', + label: 'Featured', + }, +]; + +export const organizationSortFilter: Filter[] = [ + { + value: 'relevance', + label: 'Relevance', + }, + { + value: 'challenge_count', + label: 'Challenge Count', + }, +]; diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.html b/libs/openchallenges/org-search/src/lib/org-search.component.html index 16fc7335cb..75478e209a 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.html +++ b/libs/openchallenges/org-search/src/lib/org-search.component.html @@ -1,4 +1,4 @@ -
+ diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.ts b/libs/openchallenges/org-search/src/lib/org-search.component.ts index 7ab1b5aa8d..3f524e1cec 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.ts +++ b/libs/openchallenges/org-search/src/lib/org-search.component.ts @@ -21,17 +21,17 @@ import { import { ConfigService } from '@sagebionetworks/openchallenges/config'; import { CheckboxFilterComponent, - FilterValue, + Filter, FooterComponent, OrganizationCard, OrganizationCardComponent, PaginatorComponent, } from '@sagebionetworks/openchallenges/ui'; import { - challengeContributionRolesFilter, - organizationCategoriesFilter, -} from './org-search-filters'; -import { organizationSortFilterValues } from './org-search-filters-values'; + challengeContributionRolesFilterPanel, + organizationCategoriesFilterPanel, +} from './org-search-filter-panels'; +import { organizationSortFilter } from './org-search-filters'; import { BehaviorSubject, Subject, @@ -120,11 +120,11 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { defaultPageSize = 24; // define filters - sortFilters: FilterValue[] = organizationSortFilterValues; + sortFilters: Filter[] = organizationSortFilter; // checkbox filters - contributionRolesFilter = challengeContributionRolesFilter; - categoriesFilter = organizationCategoriesFilter; + contributionRolesFilter = challengeContributionRolesFilterPanel; + categoriesFilter = organizationCategoriesFilterPanel; // define selected filter values selectedContributionRoles!: ChallengeContributionRole[]; diff --git a/libs/openchallenges/ui/src/index.ts b/libs/openchallenges/ui/src/index.ts index d2ba0b1740..b68b9553ad 100644 --- a/libs/openchallenges/ui/src/index.ts +++ b/libs/openchallenges/ui/src/index.ts @@ -5,8 +5,8 @@ export * from './lib/challenge-card/challenge-card.component'; export * from './lib/challenge-card/mock-challenges'; export * from './lib/challenge-card/mock-platforms'; export * from './lib/checkbox-filter/checkbox-filter.component'; -export * from './lib/checkbox-filter/filter-value.model'; export * from './lib/checkbox-filter/filter.model'; +export * from './lib/checkbox-filter/filter-panel.model'; export * from './lib/footer/footer.component'; export * from './lib/navbar/navbar-section'; export * from './lib/navbar/navbar.component'; diff --git a/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.html b/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.html index 64dfb92a4e..01f5920627 100644 --- a/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.html +++ b/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.html @@ -1,10 +1,10 @@ -
+
- +
diff --git a/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.ts b/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.ts index fa0477169b..55c3225345 100644 --- a/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.ts +++ b/libs/openchallenges/ui/src/lib/checkbox-filter/checkbox-filter.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { FilterValue } from './filter-value.model'; +import { Filter } from './filter.model'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { CheckboxModule } from 'primeng/checkbox'; @@ -12,8 +12,8 @@ import { CheckboxModule } from 'primeng/checkbox'; styleUrls: ['./checkbox-filter.component.scss'], }) export class CheckboxFilterComponent { - @Input({ required: true }) values!: FilterValue[]; - @Input({ required: true }) selectedValues!: string[]; + @Input({ required: true }) options!: Filter[]; + @Input({ required: true }) selectedOptions!: string[]; @Input({ required: true }) inputId!: string; @Output() selectionChange = new EventEmitter(); diff --git a/libs/openchallenges/ui/src/lib/checkbox-filter/filter-panel.model.ts b/libs/openchallenges/ui/src/lib/checkbox-filter/filter-panel.model.ts new file mode 100644 index 0000000000..2364820561 --- /dev/null +++ b/libs/openchallenges/ui/src/lib/checkbox-filter/filter-panel.model.ts @@ -0,0 +1,14 @@ +import { Filter } from './filter.model'; + +export interface FilterPanel { + /* The property name used to to query the data */ + query: string; + /* The label name of the filter */ + label: string; + /* The options of the filter. */ + options: Filter[]; + /* Whether this panel of filter is collapsed. */ + collapsed: boolean; + /* Whether to show the avatar of filter value (now only applicable to dropdown filter). */ + showAvatar?: boolean; +} diff --git a/libs/openchallenges/ui/src/lib/checkbox-filter/filter-value.model.ts b/libs/openchallenges/ui/src/lib/checkbox-filter/filter-value.model.ts deleted file mode 100644 index 4fa799b0ca..0000000000 --- a/libs/openchallenges/ui/src/lib/checkbox-filter/filter-value.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DateRange } from './data-range'; - -export interface FilterValue { - /* The value of the filter. */ - value: DateRange | string | string[] | number | undefined; - /* The display name of the filter value. */ - label: string | undefined; - /* The avatar url of the filter value. */ - avatarUrl?: string | null; -} diff --git a/libs/openchallenges/ui/src/lib/checkbox-filter/filter.model.ts b/libs/openchallenges/ui/src/lib/checkbox-filter/filter.model.ts index cd2004f689..9872be72fa 100644 --- a/libs/openchallenges/ui/src/lib/checkbox-filter/filter.model.ts +++ b/libs/openchallenges/ui/src/lib/checkbox-filter/filter.model.ts @@ -1,14 +1,12 @@ -import { FilterValue } from './filter-value.model'; +import { DateRange } from './data-range'; + +export type FilterValue = DateRange | string | number | undefined; export interface Filter { - /* The property name used to to query the data */ - query: string; - /* The label name of the filter */ - label: string; - /* The values of the filter. */ - values: FilterValue[]; - /* Whether this panel of filter is collapsed. */ - collapsed: boolean; - /* Whether to show the avatar of filter value (now only applicable to dropdown filter). */ - showAvatar?: boolean; + /* The value of the filter. */ + value: FilterValue | FilterValue[]; + /* The display name of the filter value. */ + label: string | undefined; + /* The avatar url of the filter value. */ + avatarUrl?: string | null; } diff --git a/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.html b/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.html index 9992155c3d..c5c1203255 100644 --- a/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.html +++ b/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.html @@ -1,7 +1,7 @@ - +
- - {{ value.label }} + + {{ option.label }}
diff --git a/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.ts b/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.ts index e5828b421a..32589eb6eb 100644 --- a/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.ts +++ b/libs/openchallenges/ui/src/lib/search-dropdown-filter/search-dropdown-filter.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; -import { FilterValue } from '../checkbox-filter/filter-value.model'; +import { Filter } from '../checkbox-filter/filter.model'; import { Avatar } from '../avatar/avatar'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @@ -14,8 +14,8 @@ import { MultiSelectModule } from 'primeng/multiselect'; styleUrls: ['./search-dropdown-filter.component.scss'], }) export class SearchDropdownFilterComponent implements OnInit { - @Input({ required: true }) values!: FilterValue[]; - @Input({ required: true }) selectedValues!: any[]; + @Input({ required: true }) options!: Filter[]; + @Input({ required: true }) selectedOptions!: any[]; @Input({ required: true }) placeholder = 'Search items'; @Input({ required: true }) showAvatar!: boolean | undefined; @Input({ required: true }) filterByApiClient!: boolean | undefined; @@ -48,15 +48,15 @@ export class SearchDropdownFilterComponent implements OnInit { this.searchChange.emit(this.searchTerm); } - onChange(selected: any[]): void { + onChange(selected: string[] | number[]): void { // this filter will emit as string anyways this.selectionChange.emit(selected); } - getAvatar(value: FilterValue): Avatar { + getAvatar(option: Filter): Avatar { return { - name: value.label || '', - src: value.avatarUrl ? value.avatarUrl : '', + name: option.label ?? '', + src: option.avatarUrl ? option.avatarUrl : '', size: 32, }; } From 1cda8d0863f25703ae6feebaf1b983b639577ad6 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 22:37:29 +0000 Subject: [PATCH 07/17] enable paginator filter --- .../challenge-search/src/lib/challenge-search.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html index 3490780126..843268eb42 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.html @@ -162,7 +162,7 @@

Results ({{ searchResultsCount }})

[pageSize]="selectedPageSize || defaultPageSize" [totalRecords]="searchResultsCount" [itemsPerPage]="[12, 24, 36, 48]" - (pageChange)="onPageChange($event)" + (pageChange)="onParamChange({ pageNumber: $event.page, pageSize: $event.rows,})" />
From fb2487bdfe425f711a2b5e803cbe40d3fd715888 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Wed, 25 Oct 2023 22:46:40 +0000 Subject: [PATCH 08/17] refactor date filter --- .../src/lib/challenge-search.component.ts | 47 ++++--------------- 1 file changed, 10 insertions(+), 37 deletions(-) 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 571f2c85b3..feb4a25661 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -450,12 +450,9 @@ export class ChallengeSearchComponent this.isCustomYear = (this.selectedYear as string) === 'custom'; if (!this.isCustomYear) { const yearRange = this.selectedYear as DateRange | undefined; - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - minStartDate: yearRange?.start ? yearRange.start : undefined, - maxStartDate: yearRange?.end ? yearRange.end : undefined, - }, + this.onParamChange({ + minStartDate: yearRange?.start ? yearRange.start : undefined, + maxStartDate: yearRange?.end ? yearRange.end : undefined, }); // reset custom range @@ -466,41 +463,13 @@ export class ChallengeSearchComponent onCalendarChange(): void { this.isCustomYear = true; if (this.calendar) { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - minStartDate: this.datePipe.transform( - this.calendar.value[0], - 'yyyy-MM-dd' - ), - maxStartDate: this.datePipe.transform( - this.calendar.value[1], - 'yyyy-MM-dd' - ), - }, + this.onParamChange({ + minStartDate: this.dateToFormat(this.calendar.value[0]), + maxStartDate: this.dateToFormat(this.calendar.value[1]), }); } } - onSortChange(): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - sort: this.sortedBy, - }, - }); - } - - onPageChange(event: any) { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - pageNumber: event.page, - pageSize: event.rows, - }, - }); - } - onParamChange(filteredQuery: any): void { // update params of URL const params = Object.entries(filteredQuery) @@ -530,6 +499,10 @@ export class ChallengeSearchComponent } } + dateToFormat(date: Date, format?: 'yyyy-MM-dd'): string | null { + return this.datePipe.transform(date, format); + } + splitParam(activeParam: string | undefined, by = ','): any[] { return activeParam ? activeParam.split(by) : []; } From 92ffc4115bcbe9b0bcf43edc5bbf73c702556e72 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 00:25:24 +0000 Subject: [PATCH 09/17] 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: '' }); - }) - ); - } } From 15e9004c6cf609c833d58bcbc626177a5a1b01de Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 00:45:52 +0000 Subject: [PATCH 10/17] refactor org search page --- .../src/lib/challenge-search-data.service.ts | 7 +- .../src/lib/org-search.component.html | 20 ++-- .../src/lib/org-search.component.ts | 95 +++++++------------ 3 files changed, 46 insertions(+), 76 deletions(-) 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 index be07285065..ed9a59fe40 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search-data.service.ts @@ -1,12 +1,11 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, forkJoin, iif, Observable, of, Subject } from 'rxjs'; +import { BehaviorSubject, forkJoin, iif, Observable, of } from 'rxjs'; import { catchError, debounceTime, distinctUntilChanged, map, switchMap, - takeUntil, } from 'rxjs/operators'; import { ChallengePlatformSearchQuery, @@ -35,8 +34,6 @@ export class ChallengeSearchDataService { private organizationSearchTerms: BehaviorSubject = new BehaviorSubject(''); - private destroy = new Subject(); - constructor( private challengePlatformService: ChallengePlatformService, private organizationService: OrganizationService, @@ -55,7 +52,6 @@ export class ChallengeSearchDataService { return this.platformSearchTerms.pipe( debounceTime(400), distinctUntilChanged(), - takeUntil(this.destroy), switchMap((searchTerm: string) => { const sortedBy: ChallengePlatformSort = 'name'; const platformQuery: ChallengePlatformSearchQuery = { @@ -80,7 +76,6 @@ export class ChallengeSearchDataService { return this.organizationSearchTerms.pipe( debounceTime(400), distinctUntilChanged(), - // takeUntil(this.destroy), switchMap((searchTerm: string) => { const sortBy: OrganizationSort = 'name'; const orgQuery: OrganizationSearchQuery = { diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.html b/libs/openchallenges/org-search/src/lib/org-search.component.html index 75478e209a..8ebe87a317 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.html +++ b/libs/openchallenges/org-search/src/lib/org-search.component.html @@ -1,4 +1,4 @@ - +/> diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.ts b/libs/openchallenges/org-search/src/lib/org-search.component.ts index 3f524e1cec..40b6bcebd4 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.ts +++ b/libs/openchallenges/org-search/src/lib/org-search.component.ts @@ -22,6 +22,7 @@ import { ConfigService } from '@sagebionetworks/openchallenges/config'; import { CheckboxFilterComponent, Filter, + FilterValue, FooterComponent, OrganizationCard, OrganizationCardComponent, @@ -54,7 +55,7 @@ import { import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { forkJoinConcurrent } from '@sagebionetworks/openchallenges/util'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import { CommonModule } from '@angular/common'; +import { CommonModule, Location } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { DividerModule } from 'primeng/divider'; @@ -64,6 +65,8 @@ import { PanelModule } from 'primeng/panel'; import { RadioButtonModule } from 'primeng/radiobutton'; import { SeoService } from '@sagebionetworks/shared/util'; import { getSeoData } from './org-search-seo-data'; +import { HttpParams } from '@angular/common/http'; +import { assign } from 'lodash'; @Component({ selector: 'openchallenges-org-search', @@ -138,7 +141,8 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { private readonly configService: ConfigService, private _snackBar: MatSnackBar, private seoService: SeoService, - private renderer2: Renderer2 + private renderer2: Renderer2, + private _location: Location ) { this.appVersion = this.configService.config.appVersion; this.dataUpdatedOn = this.configService.config.dataUpdatedOn; @@ -182,11 +186,7 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { this.searchTerms .pipe(debounceTime(400), distinctUntilChanged(), takeUntil(this.destroy)) .subscribe((searched) => { - const searchedTerms = searched === '' ? undefined : searched; - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { searchTerms: searchedTerms }, - }); + this.onParamChange({ searchTerms: searched }); }); const orgPage$ = this.query.pipe( @@ -240,64 +240,11 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { this.destroy.complete(); } - splitParam(activeParam: string | undefined, by = ','): any[] { - return activeParam ? activeParam.split(by) : []; - } - - collapseParam(selectedParam: any, by = ','): string | undefined { - return selectedParam.length === 0 - ? undefined - : this.splitParam(selectedParam.toString()).join(by); - } - onSearchChange(): void { // update searchTerms to trigger the query' searchTerm this.searchTerms.next(this.searchedTerms); } - onContributionRolesChange(selected: string[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - contributionRoles: this.collapseParam(selected), - }, - }); - } - - onCategoriesChange(selected: string[]): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - categories: this.collapseParam(selected), - }, - }); - } - - onSortChange(): void { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - sort: this.sortedBy, - }, - }); - } - - onPageChange(event: any) { - this.router.navigate([], { - queryParamsHandling: 'merge', - queryParams: { - pageNumber: event.page, - pageSize: event.rows, - }, - }); - } - - openSnackBar(message: string) { - this._snackBar.open(message, undefined, { - duration: 30000, - }); - } - private getOrganizationAvatarUrl(org: Organization): Observable { return iif( () => !!org.avatarKey, @@ -329,4 +276,32 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { name: org.name, } as OrganizationCard; } + + onParamChange(filteredQuery: any): void { + // update params of URL + const params = Object.entries(filteredQuery) + .map(([key, value]) => [key, this.collapseParam(value as FilterValue)]) + .reduce((obj, [key, value]) => obj.append(key, value), new HttpParams()); + this._location.replaceState(location.pathname, params.toString()); + + // update query to trigger API call + const newQuery = assign(this.query.getValue(), filteredQuery); + this.query.next(newQuery); + } + + splitParam(activeParam: string | undefined, by = ','): any[] { + return activeParam ? activeParam.split(by) : []; + } + + collapseParam(selectedParam: FilterValue | FilterValue[], by = ','): string { + return Array.isArray(selectedParam) + ? selectedParam.map((item) => item?.toString()).join(by) + : (selectedParam as string) ?? ''; + } + + openSnackBar(message: string) { + this._snackBar.open(message, undefined, { + duration: 30000, + }); + } } From 38db3b734a6d50c3480968b1f80bd79c57c3def4 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 01:09:42 +0000 Subject: [PATCH 11/17] remove empty param --- .../src/lib/challenge-search.component.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 2653d622a4..9701f3b456 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -318,8 +318,8 @@ export class ChallengeSearchComponent if (!this.isCustomYear) { const yearRange = this.selectedYear as DateRange | undefined; this.onParamChange({ - minStartDate: yearRange?.start ? yearRange.start : undefined, - maxStartDate: yearRange?.end ? yearRange.end : undefined, + minStartDate: yearRange?.start, + maxStartDate: yearRange?.end, }); // reset custom range @@ -341,7 +341,12 @@ export class ChallengeSearchComponent // update params of URL const params = Object.entries(filteredQuery) .map(([key, value]) => [key, this.collapseParam(value as FilterValue)]) - .reduce((obj, [key, value]) => obj.append(key, value), new HttpParams()); + .filter((pair) => pair !== null) + .reduce( + // update updated params, but ignore param if empty string + (obj, [key, value]) => (value !== '' ? obj.append(key, value) : obj), + new HttpParams() + ); this._location.replaceState(location.pathname, params.toString()); // update query to trigger API call From 8744a14c5aefcfe72dc6f4835028911d80cd8207 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 01:51:00 +0000 Subject: [PATCH 12/17] fix params when unselected --- .../src/lib/challenge-search.component.ts | 11 +++++++---- .../org-search/src/lib/org-search.component.ts | 10 +++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) 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 9701f3b456..21a02aba84 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts @@ -339,13 +339,16 @@ export class ChallengeSearchComponent onParamChange(filteredQuery: any): void { // update params of URL + const currentParams = new HttpParams({ + fromString: this._location.path().split('?')[1] ?? '', + }); const params = Object.entries(filteredQuery) .map(([key, value]) => [key, this.collapseParam(value as FilterValue)]) - .filter((pair) => pair !== null) .reduce( - // update updated params, but ignore param if empty string - (obj, [key, value]) => (value !== '' ? obj.append(key, value) : obj), - new HttpParams() + // update with new param, or delete the param if empty string + (params, [key, value]) => + value !== '' ? params.set(key, value) : params.delete(key), + currentParams ); this._location.replaceState(location.pathname, params.toString()); diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.ts b/libs/openchallenges/org-search/src/lib/org-search.component.ts index 40b6bcebd4..ad39bfba1f 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.ts +++ b/libs/openchallenges/org-search/src/lib/org-search.component.ts @@ -279,9 +279,17 @@ export class OrgSearchComponent implements OnInit, AfterContentInit, OnDestroy { onParamChange(filteredQuery: any): void { // update params of URL + const currentParams = new HttpParams({ + fromString: this._location.path().split('?')[1] ?? '', + }); const params = Object.entries(filteredQuery) .map(([key, value]) => [key, this.collapseParam(value as FilterValue)]) - .reduce((obj, [key, value]) => obj.append(key, value), new HttpParams()); + .reduce( + // update with new param, or delete the param if empty string + (params, [key, value]) => + value !== '' ? params.set(key, value) : params.delete(key), + currentParams + ); this._location.replaceState(location.pathname, params.toString()); // update query to trigger API call From 842a4e556f52c6e49116998bbaa25e8fa1d045a9 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Thu, 26 Oct 2023 01:52:01 +0000 Subject: [PATCH 13/17] enable scroll restoration --- apps/openchallenges/app/src/app/app.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openchallenges/app/src/app/app.config.ts b/apps/openchallenges/app/src/app/app.config.ts index 124cb5ca37..a272c2e108 100644 --- a/apps/openchallenges/app/src/app/app.config.ts +++ b/apps/openchallenges/app/src/app/app.config.ts @@ -49,7 +49,7 @@ export const appConfig: ApplicationConfig = { routes, withEnabledBlockingInitialNavigation(), withInMemoryScrolling({ - // scrollPositionRestoration: 'top', + scrollPositionRestoration: 'enabled', }) ), ], From a2912a95fdf9ce7719fe3a1864a9eb7768289101 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Fri, 27 Oct 2023 10:11:15 +0000 Subject: [PATCH 14/17] fix query for contributionRoles --- .../openchallenges/org-search/src/lib/org-search.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/openchallenges/org-search/src/lib/org-search.component.html b/libs/openchallenges/org-search/src/lib/org-search.component.html index 8ebe87a317..59a84ea8d3 100644 --- a/libs/openchallenges/org-search/src/lib/org-search.component.html +++ b/libs/openchallenges/org-search/src/lib/org-search.component.html @@ -41,7 +41,7 @@

Organizations

From 7d114e7ed62bcaa37ed31f206133b9d5aea6d5dd Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Fri, 27 Oct 2023 10:30:49 +0000 Subject: [PATCH 15/17] update client --- .../api-client-angular/src/lib/model/challengeCategory.ts | 8 ++++++-- .../api-client-angular/src/lib/model/challengeSort.ts | 8 +++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/libs/openchallenges/api-client-angular/src/lib/model/challengeCategory.ts b/libs/openchallenges/api-client-angular/src/lib/model/challengeCategory.ts index 59eb52e72d..bd06b945ab 100644 --- a/libs/openchallenges/api-client-angular/src/lib/model/challengeCategory.ts +++ b/libs/openchallenges/api-client-angular/src/lib/model/challengeCategory.ts @@ -14,9 +14,13 @@ /** * The category of the challenge. */ -export type ChallengeCategory = 'featured'; +export type ChallengeCategory = 'featured' | 'starting_soon' | 'ending_soon' | 'recently_started' | 'recently_ended'; export const ChallengeCategory = { - Featured: 'featured' as ChallengeCategory + Featured: 'featured' as ChallengeCategory, + StartingSoon: 'starting_soon' as ChallengeCategory, + EndingSoon: 'ending_soon' as ChallengeCategory, + RecentlyStarted: 'recently_started' as ChallengeCategory, + RecentlyEnded: 'recently_ended' as ChallengeCategory }; diff --git a/libs/openchallenges/api-client-angular/src/lib/model/challengeSort.ts b/libs/openchallenges/api-client-angular/src/lib/model/challengeSort.ts index bfbb699165..2310ab0d65 100644 --- a/libs/openchallenges/api-client-angular/src/lib/model/challengeSort.ts +++ b/libs/openchallenges/api-client-angular/src/lib/model/challengeSort.ts @@ -14,16 +14,14 @@ /** * What to sort results by. */ -export type ChallengeSort = 'created' | 'ending_soon' | 'random' | 'recently_ended' | 'recently_started' | 'relevance' | 'starred' | 'starting_soon'; +export type ChallengeSort = 'created' | 'random' | 'relevance' | 'starred' | 'start_date' | 'end_date'; export const ChallengeSort = { Created: 'created' as ChallengeSort, - EndingSoon: 'ending_soon' as ChallengeSort, Random: 'random' as ChallengeSort, - RecentlyEnded: 'recently_ended' as ChallengeSort, - RecentlyStarted: 'recently_started' as ChallengeSort, Relevance: 'relevance' as ChallengeSort, Starred: 'starred' as ChallengeSort, - StartingSoon: 'starting_soon' as ChallengeSort + StartDate: 'start_date' as ChallengeSort, + EndDate: 'end_date' as ChallengeSort }; From bef943852ffcbf2916f5fea96fcb870dfc61721f Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Fri, 27 Oct 2023 10:34:38 +0000 Subject: [PATCH 16/17] replace the string value by its enum value --- .../src/lib/challenge-search-filters.ts | 52 +++++++++++-------- .../org-search/src/lib/org-search-filters.ts | 17 +++--- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts b/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts index 04a6a27b3b..86b003c795 100644 --- a/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts +++ b/libs/openchallenges/challenge-search/src/lib/challenge-search-filters.ts @@ -1,3 +1,11 @@ +import { + ChallengeCategory, + ChallengeDifficulty, + ChallengeIncentive, + ChallengeSort, + ChallengeStatus, + ChallengeSubmissionType, +} from '@sagebionetworks/openchallenges/api-client-angular'; import { Filter } from '@sagebionetworks/openchallenges/ui'; const thisYear = new Date().getFullYear(); @@ -50,68 +58,68 @@ export const challengeStartYearRangeFilter: Filter[] = [ export const challengeStatusFilter: Filter[] = [ { - value: 'active', + value: ChallengeStatus.Active, label: 'Active', }, { - value: 'upcoming', + value: ChallengeStatus.Upcoming, label: 'Upcoming', }, { - value: 'completed', + value: ChallengeStatus.Completed, label: 'Completed', }, ]; export const challengeDifficultyFilter: Filter[] = [ { - value: 'good_for_beginners', + value: ChallengeDifficulty.GoodForBeginners, label: 'Good For Beginners', }, { - value: 'intermediate', + value: ChallengeDifficulty.Intermediate, label: 'Intermediate', }, { - value: 'advanced', + value: ChallengeDifficulty.Advanced, label: 'Advanced', }, ]; export const challengeSubmissionTypesFilter: Filter[] = [ { - value: 'container_image', + value: ChallengeSubmissionType.ContainerImage, label: 'Container Image', }, { - value: 'prediction_file', + value: ChallengeSubmissionType.PredictionFile, label: 'Prediction File', }, { - value: 'notebook', + value: ChallengeSubmissionType.Notebook, label: 'Notebook', }, { - value: 'other', + value: ChallengeSubmissionType.Other, label: 'Other', }, ]; export const challengeIncentivesFilter: Filter[] = [ { - value: 'monetary', + value: ChallengeIncentive.Monetary, label: 'Monetary', }, { - value: 'publication', + value: ChallengeIncentive.Publication, label: 'Publication', }, { - value: 'speaking_engagement', + value: ChallengeIncentive.SpeakingEngagement, label: 'Speaking Engagement', }, { - value: 'other', + value: ChallengeIncentive.Other, label: 'Other', }, ]; @@ -122,23 +130,23 @@ export const challengeInputDataTypesFilter: Filter[] = []; export const challengeCategoriesFilter: Filter[] = [ { - value: 'featured', + value: ChallengeCategory.Featured, label: 'Featured', }, { - value: 'starting_soon', + value: ChallengeCategory.StartingSoon, label: 'Starting Soon', }, { - value: 'ending_soon', + value: ChallengeCategory.EndingSoon, label: 'Closing Soon', }, { - value: 'recently_started', + value: ChallengeCategory.RecentlyStarted, label: 'Recently Launched', }, { - value: 'recently_ended', + value: ChallengeCategory.RecentlyEnded, label: 'Recently Completed', }, ]; @@ -149,15 +157,15 @@ export const challengeOrganizersFilter: Filter[] = []; export const challengeSortFilter: Filter[] = [ { - value: 'relevance', + value: ChallengeSort.Relevance, label: 'Relevance', }, { - value: 'start_date', + value: ChallengeSort.StartDate, label: 'Start Date', }, { - value: 'starred', + value: ChallengeSort.Starred, label: 'Most Starred', }, ]; diff --git a/libs/openchallenges/org-search/src/lib/org-search-filters.ts b/libs/openchallenges/org-search/src/lib/org-search-filters.ts index fed86989c0..4528f9d6c0 100644 --- a/libs/openchallenges/org-search/src/lib/org-search-filters.ts +++ b/libs/openchallenges/org-search/src/lib/org-search-filters.ts @@ -1,34 +1,39 @@ +import { + ChallengeContributionRole, + OrganizationCategory, + OrganizationSort, +} from '@sagebionetworks/openchallenges/api-client-angular'; import { Filter } from '@sagebionetworks/openchallenges/ui'; export const challengeContributionRolesFilter: Filter[] = [ { - value: 'challenge_organizer', + value: ChallengeContributionRole.ChallengeOrganizer, label: 'Challenge Organizer', }, { - value: 'data_contributor', + value: ChallengeContributionRole.DataContributor, label: 'Data Contributor', }, { - value: 'sponsor', + value: ChallengeContributionRole.Sponsor, label: 'Sponsor', }, ]; export const organizationCategoriesFilter: Filter[] = [ { - value: 'featured', + value: OrganizationCategory.Featured, label: 'Featured', }, ]; export const organizationSortFilter: Filter[] = [ { - value: 'relevance', + value: OrganizationSort.Relevance, label: 'Relevance', }, { - value: 'challenge_count', + value: OrganizationSort.ChallengeCount, label: 'Challenge Count', }, ]; From af1b65023c198363a5f823c9ec604ac4ef9dba98 Mon Sep 17 00:00:00 2001 From: Rongrong Chai Date: Fri, 27 Oct 2023 10:55:50 +0000 Subject: [PATCH 17/17] fix error from updating client (challengeSort) --- .../challenge-host-list/challenge-host-list.component.ts | 6 ++++-- .../featured-challenge-list.component.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/openchallenges/home/src/lib/challenge-host-list/challenge-host-list.component.ts b/libs/openchallenges/home/src/lib/challenge-host-list/challenge-host-list.component.ts index 571b7f1c83..2c3d0ae621 100644 --- a/libs/openchallenges/home/src/lib/challenge-host-list/challenge-host-list.component.ts +++ b/libs/openchallenges/home/src/lib/challenge-host-list/challenge-host-list.component.ts @@ -10,6 +10,8 @@ import { OrganizationSearchQuery, OrganizationService, Image, + OrganizationCategory, + OrganizationSort, } from '@sagebionetworks/openchallenges/api-client-angular'; import { OrganizationCard, @@ -38,9 +40,9 @@ export class ChallengeHostListComponent implements OnInit { const query: OrganizationSearchQuery = { pageNumber: 0, pageSize: 4, - categories: ['featured'], + categories: [OrganizationCategory.Featured], searchTerms: '', - sort: 'challenge_count', + sort: OrganizationSort.ChallengeCount, }; const orgPage$ = this.organizationService.listOrganizations(query).pipe( diff --git a/libs/openchallenges/home/src/lib/featured-challenge-list/featured-challenge-list.component.ts b/libs/openchallenges/home/src/lib/featured-challenge-list/featured-challenge-list.component.ts index d716d341fd..8071a71c68 100644 --- a/libs/openchallenges/home/src/lib/featured-challenge-list/featured-challenge-list.component.ts +++ b/libs/openchallenges/home/src/lib/featured-challenge-list/featured-challenge-list.component.ts @@ -4,6 +4,8 @@ import { Challenge, ChallengeService, ChallengeSearchQuery, + ChallengeCategory, + ChallengeSort, } from '@sagebionetworks/openchallenges/api-client-angular'; import { ChallengeCardComponent } from '@sagebionetworks/openchallenges/ui'; import { Observable, catchError, map, of, switchMap, throwError } from 'rxjs'; @@ -25,12 +27,12 @@ export class FeaturedChallengeListComponent implements OnInit { pageNumber: 0, pageSize: 3, // only display first 3 for now searchTerms: '', - sort: 'recently_started', + sort: ChallengeSort.StartDate, }; const query: ChallengeSearchQuery = { ...defaultQuery, - categories: ['featured'], + categories: [ChallengeCategory.Featured], }; const challengesPage$ = this.challengeService.listChallenges(query).pipe(