From 75271a94377fc1be84a1e693d14aaab3a0048ac4 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 10 Sep 2025 13:28:00 +0300 Subject: [PATCH 1/7] fix(global-search-filters): Resolved TODOs --- .../filter-chips/filter-chips.component.ts | 52 +++---------------- .../search-results-container.component.ts | 7 +-- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/src/app/shared/components/filter-chips/filter-chips.component.ts b/src/app/shared/components/filter-chips/filter-chips.component.ts index 115944df9..5d356efb4 100644 --- a/src/app/shared/components/filter-chips/filter-chips.component.ts +++ b/src/app/shared/components/filter-chips/filter-chips.component.ts @@ -29,51 +29,16 @@ export class FilterChipsComponent { }); filterOptions = computed(() => { - // [RNi]: TODO check this with paging 5 for filter options and remove comment - - // return this.filters() - // .filter((filter) => filter.key && filter.options) - // .map((filter) => ({ - // key: filter.key, - // options: filter.options!.map((opt) => ({ - // id: String(opt.value || ''), - // value: String(opt.value || ''), - // label: opt.label, - // })), - // })); - - const filtersData = this.filters(); - const cachedOptions = this.filterOptionsCache(); - const options: Record = {}; - - filtersData.forEach((filter) => { - if (filter.key && filter.options) { - options[filter.key] = filter.options.map((opt) => ({ + return this.filters() + .filter((filter) => filter.key && filter.options) + .map((filter) => ({ + key: filter.key, + options: filter.options!.map((opt) => ({ id: String(opt.value || ''), value: String(opt.value || ''), label: opt.label, - })); - } - }); - - Object.entries(cachedOptions).forEach(([filterKey, cachedOpts]) => { - if (cachedOpts && cachedOpts.length > 0) { - const existingOptions = options[filterKey] || []; - const existingValues = new Set(existingOptions.map((opt) => opt.value)); - - const newCachedOptions = cachedOpts - .filter((opt) => !existingValues.has(String(opt.value || ''))) - .map((opt) => ({ - id: String(opt.value || ''), - value: String(opt.value || ''), - label: opt.label, - })); - - options[filterKey] = [...newCachedOptions, ...existingOptions]; - } - }); - - return options; + })), + })); }); chips = computed(() => { @@ -85,8 +50,7 @@ export class FilterChipsComponent { .filter(([, value]) => value !== null && value !== '') .map(([key, value]) => { const filterLabel = labels.find((l) => l.key === key)?.label || key; - //const filterOptionsList = options.find((o) => o.key === key)?.options || []; - const filterOptionsList = options[key] || []; + const filterOptionsList = options.find((o) => o.key === key)?.options || []; const option = filterOptionsList.find((opt) => opt.value === value || opt.id === value); const displayValue = option?.label || value || ''; diff --git a/src/app/shared/components/search-results-container/search-results-container.component.ts b/src/app/shared/components/search-results-container/search-results-container.component.ts index 11502fb0f..69cb9ad35 100644 --- a/src/app/shared/components/search-results-container/search-results-container.component.ts +++ b/src/app/shared/components/search-results-container/search-results-container.component.ts @@ -20,7 +20,7 @@ import { FormsModule } from '@angular/forms'; import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { searchSortingOptions } from '@osf/shared/constants'; import { ResourceType } from '@osf/shared/enums'; -import { ResourceModel, TabOption } from '@osf/shared/models'; +import { DiscoverableFilter, ResourceModel, TabOption } from '@osf/shared/models'; import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component'; import { ResourceCardComponent } from '../resource-card/resource-card.component'; @@ -48,6 +48,7 @@ import { SelectComponent } from '../select/select.component'; export class SearchResultsContainerComponent { resources = input([]); areResourcesLoading = input(false); + filters = input([]); searchCount = input(0); selectedSort = input(''); selectedTab = input(ResourceType.Null); @@ -78,9 +79,9 @@ export class SearchResultsContainerComponent { }); protected readonly hasFilters = computed(() => { - //[RNi] TODO: check if there are any filters - return true; + return this.filters().length > 0; }); + filtersComponent = contentChild>('filtersComponent'); selectSort(value: string): void { From 5604f90ead901f31d245a90a858d1986d43da9b9 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 10 Sep 2025 13:30:47 +0300 Subject: [PATCH 2/7] fix(global-search-filters): Fixed and simplified filters logic code --- .../generic-filter.component.html | 18 +++-------------- .../generic-filter.component.ts | 4 ---- .../global-search.component.html | 1 + .../reusable-filter.component.html | 16 +++++++-------- .../reusable-filter.component.ts | 20 ------------------- .../shared/services/global-search.service.ts | 10 ++++------ .../global-search/global-search.state.ts | 4 ++-- 7 files changed, 18 insertions(+), 55 deletions(-) diff --git a/src/app/shared/components/generic-filter/generic-filter.component.html b/src/app/shared/components/generic-filter/generic-filter.component.html index 9ab2cfbf5..36d4c4c10 100644 --- a/src/app/shared/components/generic-filter/generic-filter.component.html +++ b/src/app/shared/components/generic-filter/generic-filter.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/shared/components/generic-filter/generic-filter.component.ts b/src/app/shared/components/generic-filter/generic-filter.component.ts index 6245fd910..75942cbe6 100644 --- a/src/app/shared/components/generic-filter/generic-filter.component.ts +++ b/src/app/shared/components/generic-filter/generic-filter.component.ts @@ -162,10 +162,6 @@ export class GenericFilterComponent { } } - trackByOption(index: number, option: SelectOption): string { - return option.value?.toString() || index.toString(); - } - onValueChange(event: SelectChangeEvent): void { const options = this.filterOptions(); const selectedOption = event.value ? options.find((opt) => opt.value === event.value) : null; diff --git a/src/app/shared/components/global-search/global-search.component.html b/src/app/shared/components/global-search/global-search.component.html index f2a841daf..fb83ead46 100644 --- a/src/app/shared/components/global-search/global-search.component.html +++ b/src/app/shared/components/global-search/global-search.component.html @@ -11,6 +11,7 @@ {{ getFilterLabel(filter) }} - @if (getFilterDescription(filter)) { -

{{ getFilterDescription(filter) }}

+ @if (filter.description) { +

{{ filter.description }}

} - @if (getFilterHelpLink(filter) && getFilterHelpLinkText(filter)) { + @if (filter.helpLink && filter.helpLinkText) {

- {{ getFilterHelpLinkText(filter) }} + {{ filter.helpLinkText }}

} @@ -33,9 +33,9 @@ item.type === 'index-card' && !!item.attributes?.resourceMetadata - ); + const filterOptionItems = response.included!.filter( + (item): item is FilterOptionItem => item.type === 'index-card' && !!item.attributes?.resourceMetadata + ); - options.push(...filterOptionItems.map((item) => mapFilterOption(item))); - } + options.push(...filterOptionItems.map((item) => mapFilterOption(item))); const searchResultPage = response?.data?.relationships?.['searchResultPage'] as { links?: { next?: { href: string } }; diff --git a/src/app/shared/stores/global-search/global-search.state.ts b/src/app/shared/stores/global-search/global-search.state.ts index 034bba50e..82342dffe 100644 --- a/src/app/shared/stores/global-search/global-search.state.ts +++ b/src/app/shared/stores/global-search/global-search.state.ts @@ -141,9 +141,9 @@ export class GlobalSearchState { @Action(LoadFilterOptionsWithSearch) loadFilterOptionsWithSearch(ctx: StateContext, action: LoadFilterOptionsWithSearch) { const state = ctx.getState(); + const filterKey = action.filterKey; const loadingFilters = state.filters.map((f) => (f.key === filterKey ? { ...f, isSearchLoading: true } : f)); ctx.patchState({ filters: loadingFilters }); - const filterKey = action.filterKey; return this.searchService .getFilterOptions(this.buildParamsForIndexValueSearch(state, filterKey, action.searchText)) .pipe( @@ -288,7 +288,7 @@ export class GlobalSearchState { ): Record { return { ...this.buildParamsForIndexCardSearch(state), - 'page[size]': '50', + 'page[size]': '200', valueSearchPropertyPath: filterKey, valueSearchText: valueSearchText ?? '', }; From 1f387cc85015a9214c9a8e994dada19ed0438bb4 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 10 Sep 2025 13:31:43 +0300 Subject: [PATCH 3/7] fix(global-search-resource-card): Made links open in a new tab --- .../file-secondary-metadata.component.html | 14 ++++++--- ...preprint-secondary-metadata.component.html | 31 +++++++++++++------ .../project-secondary-metadata.component.html | 27 +++++++++++----- ...stration-secondary-metadata.component.html | 22 ++++++++++--- .../resource-card.component.html | 14 ++++++--- 5 files changed, 78 insertions(+), 30 deletions(-) diff --git a/src/app/shared/components/resource-card/components/file-secondary-metadata/file-secondary-metadata.component.html b/src/app/shared/components/resource-card/components/file-secondary-metadata/file-secondary-metadata.component.html index 3bbee3553..f004432e6 100644 --- a/src/app/shared/components/resource-card/components/file-secondary-metadata/file-secondary-metadata.component.html +++ b/src/app/shared/components/resource-card/components/file-secondary-metadata/file-secondary-metadata.component.html @@ -10,7 +10,9 @@

{{ 'resourceCard.labels.funder' | translate }} @for (funder of nodeFunders.slice(0, limit); track $index) { - {{ funder.name }}{{ $last ? '' : ', ' }} + {{ funder.name }}{{ $last ? '' : ', ' }} } @if (nodeFunders.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: nodeFunders.length - limit } }} @@ -26,14 +28,18 @@ @if (nodeLicense) {

{{ 'resourceCard.labels.license' | translate }} - {{ nodeLicense!.name }} + {{ + nodeLicense!.name + }}

} @if (resourceValue.absoluteUrl) {

{{ 'resourceCard.labels.url' | translate }} - {{ resourceValue.absoluteUrl }} + {{ + resourceValue.absoluteUrl + }}

} @@ -41,7 +47,7 @@

{{ 'resourceCard.labels.doi' | translate }} @for (doi of resourceValue.doi.slice(0, limit); track $index) { - {{ doi }}{{ $last ? '' : ', ' }} + {{ doi }}{{ $last ? '' : ', ' }} } @if (resourceValue.doi.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.doi.length - limit } }} diff --git a/src/app/shared/components/resource-card/components/preprint-secondary-metadata/preprint-secondary-metadata.component.html b/src/app/shared/components/resource-card/components/preprint-secondary-metadata/preprint-secondary-metadata.component.html index 6e0dc23e3..fb621f2ba 100644 --- a/src/app/shared/components/resource-card/components/preprint-secondary-metadata/preprint-secondary-metadata.component.html +++ b/src/app/shared/components/resource-card/components/preprint-secondary-metadata/preprint-secondary-metadata.component.html @@ -7,14 +7,16 @@ @if (resourceValue.provider) {

{{ 'resourceCard.labels.provider' | translate }} - {{ resourceValue.provider!.name }} + {{ + resourceValue.provider!.name + }}

} @if (resourceValue.hasDataResource) {

{{ 'resourceCard.labels.associatedData' | translate }} - + {{ resourceValue.hasDataResource }}

@@ -23,7 +25,12 @@ @if (resourceValue.hasPreregisteredAnalysisPlan) {

{{ 'resourceCard.labels.associatedAnalysisPlan' | translate }} - + {{ resourceValue.hasPreregisteredAnalysisPlan }}

@@ -32,7 +39,7 @@ @if (resourceValue.hasPreregisteredStudyDesign) {

{{ 'resourceCard.labels.associatedStudyDesign' | translate }} - + {{ resourceValue.hasPreregisteredStudyDesign }}

@@ -53,16 +60,22 @@ @if (resourceValue.license?.absoluteUrl) {

{{ 'resourceCard.labels.license' | translate }} - {{ - resourceValue.license!.name - }} + {{ resourceValue.license!.name }}

} @if (resourceValue.absoluteUrl) {

{{ 'resourceCard.labels.url' | translate }} - {{ resourceValue.absoluteUrl }} + {{ + resourceValue.absoluteUrl + }}

} @@ -71,7 +84,7 @@

{{ 'resourceCard.labels.doi' | translate }} @for (doi of resourceValue.doi.slice(0, limit); track $index) { - {{ doi }}{{ $last ? '' : ', ' }} + {{ doi }}{{ $last ? '' : ', ' }} } @if (resourceValue.doi.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.doi.length - limit } }} diff --git a/src/app/shared/components/resource-card/components/project-secondary-metadata/project-secondary-metadata.component.html b/src/app/shared/components/resource-card/components/project-secondary-metadata/project-secondary-metadata.component.html index 17e0c25d9..4184dfcb7 100644 --- a/src/app/shared/components/resource-card/components/project-secondary-metadata/project-secondary-metadata.component.html +++ b/src/app/shared/components/resource-card/components/project-secondary-metadata/project-secondary-metadata.component.html @@ -8,7 +8,9 @@

{{ 'resourceCard.labels.funder' | translate }} @for (funder of resourceValue.funders.slice(0, limit); track $index) { - {{ funder.name }}{{ $last ? '' : ', ' }} + {{ funder.name }}{{ $last ? '' : ', ' }} } @if (resourceValue.funders.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.funders.length - limit } }} @@ -23,7 +25,12 @@ @if (resourceValue.isPartOfCollection) {

{{ 'resourceCard.labels.collection' | translate }} - + {{ resourceValue.isPartOfCollection!.name }}

@@ -36,16 +43,22 @@ @if (resourceValue.license) {

{{ 'resourceCard.labels.license' | translate }} - {{ - resourceValue.license!.name - }} + {{ resourceValue.license!.name }}

} @if (resourceValue.absoluteUrl) {

{{ 'resourceCard.labels.url' | translate }} - {{ resourceValue.absoluteUrl }} + {{ + resourceValue.absoluteUrl + }}

} @@ -53,7 +66,7 @@

{{ 'resourceCard.labels.doi' | translate }} @for (doi of resourceValue.doi.slice(0, limit); track $index) { - {{ doi }}{{ $last ? '' : ', ' }} + {{ doi }}{{ $last ? '' : ', ' }} } @if (resourceValue.doi.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.doi.length - limit } }} diff --git a/src/app/shared/components/resource-card/components/registration-secondary-metadata/registration-secondary-metadata.component.html b/src/app/shared/components/resource-card/components/registration-secondary-metadata/registration-secondary-metadata.component.html index 84c00739c..68fdcf9ce 100644 --- a/src/app/shared/components/resource-card/components/registration-secondary-metadata/registration-secondary-metadata.component.html +++ b/src/app/shared/components/resource-card/components/registration-secondary-metadata/registration-secondary-metadata.component.html @@ -8,7 +8,9 @@

{{ 'resourceCard.labels.funder' | translate }} @for (funder of resourceValue.funders.slice(0, limit); track $index) { - {{ funder.name }}{{ $last ? '' : ', ' }} + {{ funder.name }}{{ $last ? '' : ', ' }} } @if (resourceValue.funders.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.funders.length - limit } }} @@ -19,7 +21,9 @@ @if (resourceValue.provider) {

{{ 'resourceCard.labels.provider' | translate }} - {{ resourceValue.provider!.name }} + {{ + resourceValue.provider!.name + }}

} @@ -30,14 +34,22 @@ @if (resourceValue.license) {

{{ 'resourceCard.labels.license' | translate }} - {{ resourceValue.license!.name }} + {{ resourceValue.license!.name }}

} @if (resourceValue.absoluteUrl) {

{{ 'resourceCard.labels.url' | translate }} - {{ resourceValue.absoluteUrl }} + {{ + resourceValue.absoluteUrl + }}

} @@ -46,7 +58,7 @@

{{ 'resourceCard.labels.doi' | translate }} @for (doi of resourceValue.doi.slice(0, limit); track $index) { - {{ doi }}{{ $last ? '' : ', ' }} + {{ doi }}{{ $last ? '' : ', ' }} } @if (resourceValue.doi.length > limit) { {{ 'resourceCard.andCountMore' | translate: { count: resourceValue.doi.length - limit } }} diff --git a/src/app/shared/components/resource-card/resource-card.component.html b/src/app/shared/components/resource-card/resource-card.component.html index 352a8ee00..1d24d60d6 100644 --- a/src/app/shared/components/resource-card/resource-card.component.html +++ b/src/app/shared/components/resource-card/resource-card.component.html @@ -7,14 +7,14 @@

- {{ displayTitle() }} + {{ displayTitle() }}

@if (isWithdrawn()) { {{ 'resourceCard.labels.withdrawn' | translate }} } @let orcidValues = orcids(); @if (orcidValues.length && orcidValues[0]) { - + orcid } @@ -24,7 +24,7 @@

@if (affiliatedEntities().length > 0) {
@for (affiliatedEntity of affiliatedEntities().slice(0, limit); track $index) { - {{ affiliatedEntity.name }}{{ $last ? '' : ', ' }} } @@ -39,14 +39,18 @@

@if (resource().isPartOf) {

{{ 'resourceCard.labels.from' | translate }}

- {{ resource().isPartOf!.name }} + {{ + resource().isPartOf!.name + }}
} @if (resource().isContainedBy) {

{{ 'resourceCard.labels.from' | translate }}

- {{ resource().isContainedBy!.name }} + {{ + resource().isContainedBy!.name + }}
} From 6899dedd320567ca2c43099c0784bbc7ed1765b7 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 10 Sep 2025 16:01:23 +0300 Subject: [PATCH 4/7] fix(global-search-filters): Added cardSearchResultCount to filter option --- .../generic-filter.component.html | 7 ++- .../generic-filter.component.ts | 21 ++++--- .../reusable-filter.component.ts | 8 +-- .../mappers/filters/filter-option.mapper.ts | 62 +++++++++++-------- .../search/discaverable-filter.model.ts | 8 ++- .../search/filter-options-json-api.models.ts | 4 +- .../index-card-search-json-api.models.ts | 3 + .../shared/services/global-search.service.ts | 21 ++++--- .../global-search/global-search.model.ts | 6 +- 9 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/app/shared/components/generic-filter/generic-filter.component.html b/src/app/shared/components/generic-filter/generic-filter.component.html index 36d4c4c10..bb275b0a4 100644 --- a/src/app/shared/components/generic-filter/generic-filter.component.html +++ b/src/app/shared/components/generic-filter/generic-filter.component.html @@ -9,7 +9,6 @@ [id]="filterType()" [options]="filterOptions()" optionLabel="label" - optionValue="value" [ngModel]="selectedValue()" [placeholder]="currentSelectedOption() ? currentSelectedOption()?.label : placeholder()" styleClass="w-full" @@ -27,7 +26,11 @@ (onChange)="onValueChange($event)" (onFilter)="onFilterChange($event)" [showClear]="!!selectedValue()" - /> + > + +

{{ item.label }} ({{ item.cardSearchResultCount }})

+
+

}

diff --git a/src/app/shared/components/generic-filter/generic-filter.component.ts b/src/app/shared/components/generic-filter/generic-filter.component.ts index 75942cbe6..421ffdea3 100644 --- a/src/app/shared/components/generic-filter/generic-filter.component.ts +++ b/src/app/shared/components/generic-filter/generic-filter.component.ts @@ -17,7 +17,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { LoadingSpinnerComponent } from '@shared/components'; -import { SelectOption } from '@shared/models'; +import { FilterOption, SelectOption } from '@shared/models'; @Component({ selector: 'osf-generic-filter', @@ -28,8 +28,8 @@ import { SelectOption } from '@shared/models'; }) export class GenericFilterComponent { private destroyRef = inject(DestroyRef); - options = input([]); - searchResults = input([]); + options = input([]); + searchResults = input([]); isLoading = input(false); isPaginationLoading = input(false); isSearchLoading = input(false); @@ -41,12 +41,12 @@ export class GenericFilterComponent { searchTextChanged = output(); loadMoreOptions = output(); - currentSelectedOption = signal(null); + currentSelectedOption = signal(null); private searchSubject = new Subject(); private currentSearchText = signal(''); - private searchResultOptions = signal([]); + private searchResultOptions = signal([]); private isActivelySearching = signal(false); - private stableOptionsArray: SelectOption[] = []; + private stableOptionsArray: FilterOption[] = []; filterOptions = computed(() => { const searchResults = this.searchResultOptions(); @@ -58,7 +58,7 @@ export class GenericFilterComponent { } const baseOptions = this.formatOptions(parentOptions); - let newOptions: SelectOption[]; + let newOptions: FilterOption[]; if (searchResults.length > 0) { const searchFormatted = this.formatOptions(searchResults); @@ -73,13 +73,13 @@ export class GenericFilterComponent { return this.stableOptionsArray; }); - private formatOptions(options: SelectOption[]): SelectOption[] { + private formatOptions(options: FilterOption[]): FilterOption[] { if (options.length > 0) { if (this.filterType() === 'dateCreated') { return options .filter((option) => option?.label) - .sort((a, b) => b.label.localeCompare(a.label)) .map((option) => ({ + ...option, label: option.label || '', value: option.label || '', })); @@ -88,6 +88,7 @@ export class GenericFilterComponent { .filter((option) => option?.label) .sort((a, b) => a.label.localeCompare(b.label)) .map((option) => ({ + ...option, label: option.label || '', value: option.value || '', })); @@ -106,7 +107,7 @@ export class GenericFilterComponent { return true; } - private updateStableArray(newOptions: SelectOption[]): void { + private updateStableArray(newOptions: FilterOption[]): void { if (this.arraysEqual(this.stableOptionsArray, newOptions)) { return; } diff --git a/src/app/shared/components/reusable-filter/reusable-filter.component.ts b/src/app/shared/components/reusable-filter/reusable-filter.component.ts index a72d9bf7c..3668ec91c 100644 --- a/src/app/shared/components/reusable-filter/reusable-filter.component.ts +++ b/src/app/shared/components/reusable-filter/reusable-filter.component.ts @@ -11,7 +11,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { LoadingSpinnerComponent } from '@shared/components'; import { FILTER_PLACEHOLDERS } from '@shared/constants/filter-placeholders'; import { StringOrNull } from '@shared/helpers'; -import { DiscoverableFilter, SelectOption } from '@shared/models'; +import { DiscoverableFilter, FilterOption } from '@shared/models'; import { GenericFilterComponent } from '../generic-filter/generic-filter.component'; @@ -37,7 +37,7 @@ import { GenericFilterComponent } from '../generic-filter/generic-filter.compone export class ReusableFilterComponent { filters = input([]); selectedValues = input>({}); - filterSearchResults = input>({}); + filterSearchResults = input>({}); isLoading = input(false); showEmptyState = input(true); plainStyle = input(false); @@ -152,11 +152,11 @@ export class ReusableFilterComponent { } } - getFilterOptions(filter: DiscoverableFilter): SelectOption[] { + getFilterOptions(filter: DiscoverableFilter): FilterOption[] { return filter.options || []; } - getFilterSearchResults(filter: DiscoverableFilter): SelectOption[] { + getFilterSearchResults(filter: DiscoverableFilter): FilterOption[] { const searchResults = this.filterSearchResults(); return searchResults[filter.key] || []; } diff --git a/src/app/shared/mappers/filters/filter-option.mapper.ts b/src/app/shared/mappers/filters/filter-option.mapper.ts index 0f62a61ec..9418fe108 100644 --- a/src/app/shared/mappers/filters/filter-option.mapper.ts +++ b/src/app/shared/mappers/filters/filter-option.mapper.ts @@ -1,29 +1,39 @@ -import { FilterOptionItem, SelectOption } from '@shared/models'; +import { FilterOption, FilterOptionItem, SearchResultJsonApi } from '@shared/models'; -export function mapFilterOption(item: FilterOptionItem): SelectOption { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const metadata: any = item.attributes.resourceMetadata; - const id = metadata['@id']; +export function mapFilterOptions( + searchResultItems: SearchResultJsonApi[], + filterOptionItems: FilterOptionItem[] +): FilterOption[] { + return searchResultItems.map((searchResult) => { + const cardSearchResultCount = searchResult.attributes!.cardSearchResultCount; + const filterOption = filterOptionItems.find((option) => option.id === searchResult.relationships.indexCard.data.id); + const filterOptionMetadata = filterOption?.attributes.resourceMetadata; + const id = filterOptionMetadata['@id']; - if ('title' in metadata) { - return { - label: metadata?.title?.[0]?.['@value'], - value: id, - }; - } else if ('displayLabel' in metadata) { - return { - label: metadata.displayLabel?.[0]?.['@value'], - value: id, - }; - } else if ('name' in metadata) { - return { - label: metadata.name?.[0]?.['@value'], - value: id, - }; - } else { - return { - label: '', - value: id, - }; - } + if ('title' in filterOptionMetadata) { + return { + label: filterOptionMetadata?.title?.[0]?.['@value'], + value: id, + cardSearchResultCount, + }; + } else if ('displayLabel' in filterOptionMetadata) { + return { + label: filterOptionMetadata.displayLabel?.[0]?.['@value'], + value: id, + cardSearchResultCount, + }; + } else if ('name' in filterOptionMetadata) { + return { + label: filterOptionMetadata.name?.[0]?.['@value'], + value: id, + cardSearchResultCount, + }; + } else { + return { + label: '', + value: id, + cardSearchResultCount, + }; + } + }); } diff --git a/src/app/shared/models/search/discaverable-filter.model.ts b/src/app/shared/models/search/discaverable-filter.model.ts index 80c57e034..3ffd12d64 100644 --- a/src/app/shared/models/search/discaverable-filter.model.ts +++ b/src/app/shared/models/search/discaverable-filter.model.ts @@ -5,8 +5,8 @@ export interface DiscoverableFilter { label: string; type: 'select' | 'date' | 'checkbox' | 'group'; operator: string; - options?: SelectOption[]; - selectedValues?: SelectOption[]; + options?: FilterOption[]; + selectedValues?: FilterOption[]; description?: string; helpLink?: string; helpLinkText?: string; @@ -19,3 +19,7 @@ export interface DiscoverableFilter { loadOptionsOnExpand?: boolean; filters?: DiscoverableFilter[]; } + +export interface FilterOption extends SelectOption { + cardSearchResultCount: number; +} diff --git a/src/app/shared/models/search/filter-options-json-api.models.ts b/src/app/shared/models/search/filter-options-json-api.models.ts index e10db93d5..00073f061 100644 --- a/src/app/shared/models/search/filter-options-json-api.models.ts +++ b/src/app/shared/models/search/filter-options-json-api.models.ts @@ -1,8 +1,10 @@ +import { SearchResultJsonApi } from '@shared/models'; + import { ApiData } from '../common'; export interface FilterOptionsResponseJsonApi { data: FilterOptionsResponseData; - included?: FilterOptionItem[]; + included?: (FilterOptionItem | SearchResultJsonApi)[]; links?: { first?: string; next?: string; diff --git a/src/app/shared/models/search/index-card-search-json-api.models.ts b/src/app/shared/models/search/index-card-search-json-api.models.ts index 8ca55bb5a..5f4c3154e 100644 --- a/src/app/shared/models/search/index-card-search-json-api.models.ts +++ b/src/app/shared/models/search/index-card-search-json-api.models.ts @@ -40,6 +40,9 @@ export interface SearchResultJsonApi { }; }; }; + attributes?: { + cardSearchResultCount: number; + }; } export type IndexCardDataJsonApi = ApiData; diff --git a/src/app/shared/services/global-search.service.ts b/src/app/shared/services/global-search.service.ts index 1e34ec45d..74c5434aa 100644 --- a/src/app/shared/services/global-search.service.ts +++ b/src/app/shared/services/global-search.service.ts @@ -5,16 +5,16 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/shared/services'; import { MapResources } from '@shared/mappers/search'; import { + FilterOption, FilterOptionItem, FilterOptionsResponseJsonApi, IndexCardDataJsonApi, IndexCardSearchResponseJsonApi, ResourcesData, SearchResultJsonApi, - SelectOption, } from '@shared/models'; -import { AppliedFilter, CombinedFilterMapper, mapFilterOption, RelatedPropertyPathItem } from '../mappers'; +import { AppliedFilter, CombinedFilterMapper, mapFilterOptions, RelatedPropertyPathItem } from '../mappers'; import { environment } from 'src/environments/environment'; @@ -36,30 +36,31 @@ export class GlobalSearchService { .pipe(map((response) => this.handleResourcesRawResponse(response))); } - getFilterOptions(params: Record): Observable<{ options: SelectOption[]; nextUrl?: string }> { + getFilterOptions(params: Record): Observable<{ options: FilterOption[]; nextUrl?: string }> { return this.jsonApiService .get(`${environment.shareTroveUrl}/index-value-search`, params) .pipe(map((response) => this.handleFilterOptionsRawResponse(response))); } - getFilterOptionsFromPaginationUrl(url: string): Observable<{ options: SelectOption[]; nextUrl?: string }> { + getFilterOptionsFromPaginationUrl(url: string): Observable<{ options: FilterOption[]; nextUrl?: string }> { return this.jsonApiService .get(url) .pipe(map((response) => this.handleFilterOptionsRawResponse(response))); } private handleFilterOptionsRawResponse(response: FilterOptionsResponseJsonApi): { - options: SelectOption[]; + options: FilterOption[]; nextUrl?: string; } { - const options: SelectOption[] = []; + const options: FilterOption[] = []; let nextUrl: string | undefined; - const filterOptionItems = response.included!.filter( - (item): item is FilterOptionItem => item.type === 'index-card' && !!item.attributes?.resourceMetadata - ); + const searchResultItems = response + .included!.filter((item): item is SearchResultJsonApi => item.type === 'search-result') + .sort((a, b) => Number(a.id.at(-1)) - Number(b.id.at(-1))); + const filterOptionItems = response.included!.filter((item): item is FilterOptionItem => item.type === 'index-card'); - options.push(...filterOptionItems.map((item) => mapFilterOption(item))); + options.push(...mapFilterOptions(searchResultItems, filterOptionItems)); const searchResultPage = response?.data?.relationships?.['searchResultPage'] as { links?: { next?: { href: string } }; diff --git a/src/app/shared/stores/global-search/global-search.model.ts b/src/app/shared/stores/global-search/global-search.model.ts index af1404f75..98de2e0b3 100644 --- a/src/app/shared/stores/global-search/global-search.model.ts +++ b/src/app/shared/stores/global-search/global-search.model.ts @@ -1,14 +1,14 @@ import { ResourceType } from '@osf/shared/enums'; import { StringOrNull } from '@osf/shared/helpers'; -import { AsyncStateModel, DiscoverableFilter, ResourceModel, SelectOption } from '@osf/shared/models'; +import { AsyncStateModel, DiscoverableFilter, FilterOption, ResourceModel } from '@osf/shared/models'; export interface GlobalSearchStateModel { resources: AsyncStateModel; filters: DiscoverableFilter[]; defaultFilterValues: Record; filterValues: Record; - filterOptionsCache: Record; - filterSearchCache: Record; + filterOptionsCache: Record; + filterSearchCache: Record; filterPaginationCache: Record; resourcesCount: number; searchText: StringOrNull; From 4e6ffeef2f3c7bbaf4b20be4e6833f8c4ee96157 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 10 Sep 2025 17:01:53 +0300 Subject: [PATCH 5/7] fix(global-search-filters): Fixed boolean filters selected state --- .../reusable-filter/reusable-filter.component.html | 10 ++++------ .../reusable-filter/reusable-filter.component.ts | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/app/shared/components/reusable-filter/reusable-filter.component.html b/src/app/shared/components/reusable-filter/reusable-filter.component.html index 2fb943aec..7273ac224 100644 --- a/src/app/shared/components/reusable-filter/reusable-filter.component.html +++ b/src/app/shared/components/reusable-filter/reusable-filter.component.html @@ -58,16 +58,14 @@ @for (filter of group.filters; track filter.key) {
-
} diff --git a/src/app/shared/components/reusable-filter/reusable-filter.component.ts b/src/app/shared/components/reusable-filter/reusable-filter.component.ts index 3668ec91c..74a74d02a 100644 --- a/src/app/shared/components/reusable-filter/reusable-filter.component.ts +++ b/src/app/shared/components/reusable-filter/reusable-filter.component.ts @@ -6,7 +6,7 @@ import { Checkbox, CheckboxChangeEvent } from 'primeng/checkbox'; import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { LoadingSpinnerComponent } from '@shared/components'; import { FILTER_PLACEHOLDERS } from '@shared/constants/filter-placeholders'; @@ -29,6 +29,7 @@ import { GenericFilterComponent } from '../generic-filter/generic-filter.compone LoadingSpinnerComponent, Checkbox, NgClass, + FormsModule, ], templateUrl: './reusable-filter.component.html', styleUrls: ['./reusable-filter.component.scss'], @@ -42,6 +43,8 @@ export class ReusableFilterComponent { showEmptyState = input(true); plainStyle = input(false); + readonly Boolean = Boolean; + loadFilterOptions = output(); filterValueChanged = output<{ filterType: string; value: StringOrNull }>(); filterSearchChanged = output<{ filterType: string; searchText: string; filter: DiscoverableFilter }>(); @@ -197,9 +200,4 @@ export class ReusableFilterComponent { const isChecked = event?.checked || false; this.onIsPresentFilterToggle(filter, isChecked); } - - isIsPresentFilterChecked(filterKey: string): boolean { - const selectedValue = this.selectedValues()[filterKey]; - return selectedValue === 'true' || Boolean(selectedValue); - } } From 1bed1bf53f1b42f94bcea041f1d5a137e582d6cf Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 11 Sep 2025 12:35:43 +0300 Subject: [PATCH 6/7] fix(global-search-filters): Fixed after merge conflict --- .../components/generic-filter/generic-filter.component.ts | 2 +- .../shared/stores/global-search/global-search.selectors.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/components/generic-filter/generic-filter.component.ts b/src/app/shared/components/generic-filter/generic-filter.component.ts index 435b2af4b..694802fcb 100644 --- a/src/app/shared/components/generic-filter/generic-filter.component.ts +++ b/src/app/shared/components/generic-filter/generic-filter.component.ts @@ -98,7 +98,7 @@ export class GenericFilterComponent { return []; } - private arraysEqual(a: SelectOption[], b: SelectOption[]): boolean { + private arraysEqual(a: FilterOption[], b: FilterOption[]): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i].value !== b[i].value || a[i].label !== b[i].label) { diff --git a/src/app/shared/stores/global-search/global-search.selectors.ts b/src/app/shared/stores/global-search/global-search.selectors.ts index d9bb6cba2..ec60a0742 100644 --- a/src/app/shared/stores/global-search/global-search.selectors.ts +++ b/src/app/shared/stores/global-search/global-search.selectors.ts @@ -2,7 +2,7 @@ import { Selector } from '@ngxs/store'; import { ResourceType } from '@osf/shared/enums'; import { StringOrNull } from '@osf/shared/helpers'; -import { DiscoverableFilter, ResourceModel, SelectOption } from '@osf/shared/models'; +import { DiscoverableFilter, FilterOption, ResourceModel } from '@osf/shared/models'; import { GlobalSearchStateModel } from './global-search.model'; import { GlobalSearchState } from './global-search.state'; @@ -69,12 +69,12 @@ export class GlobalSearchSelectors { } @Selector([GlobalSearchState]) - static getFilterOptionsCache(state: GlobalSearchStateModel): Record { + static getFilterOptionsCache(state: GlobalSearchStateModel): Record { return state.filterOptionsCache; } @Selector([GlobalSearchState]) - static getFilterSearchCache(state: GlobalSearchStateModel): Record { + static getFilterSearchCache(state: GlobalSearchStateModel): Record { return state.filterSearchCache; } From f514e9cd6fea0095378aeac66678783489d66b3a Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 11 Sep 2025 15:29:23 +0300 Subject: [PATCH 7/7] fix(global-search-filters): Fixed PR comments --- .../reusable-filter/reusable-filter.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/components/reusable-filter/reusable-filter.component.html b/src/app/shared/components/reusable-filter/reusable-filter.component.html index 7273ac224..5cb3ca3fa 100644 --- a/src/app/shared/components/reusable-filter/reusable-filter.component.html +++ b/src/app/shared/components/reusable-filter/reusable-filter.component.html @@ -33,9 +33,9 @@ }