Skip to content

Commit

Permalink
Merge pull request #513 from geonetwork/predefined-searches-under-sea…
Browse files Browse the repository at this point in the history
…rchbar

Add ability to predefine specific search in config
  • Loading branch information
Angi-Kinas authored Jul 5, 2023
2 parents 2404ff5 + 3a7293f commit 6487b30
Show file tree
Hide file tree
Showing 16 changed files with 224 additions and 47 deletions.
2 changes: 1 addition & 1 deletion apps/datahub/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class AppModule {
ThemeService.generateBgOpacityClasses(
'primary',
getThemeConfig().PRIMARY_COLOR,
[10, 25, 75]
[10, 25, 50, 75, 100]
)
}
}
19 changes: 15 additions & 4 deletions apps/datahub/src/app/home/home-header/home-header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,35 @@
[toggled]="searchFacade.favoritesOnly$ | async"
(action)="listFavorites($event)"
></datahub-header-badge-button>
<div *ngIf="displaySortBadges$ | async">
<div>
<button
type="button"
class="badge-btn bg-primary-opacity-25 hover-bg-primary-opacity-75"
class="badge-btn bg-primary-opacity-50 hover-bg-primary-opacity-100"
(click)="clearSearchAndSort(SORT_BY_PARAMS.CREATE_DATE)"
>
<span translate="">datahub.header.lastRecords</span>
</button>
</div>
<div *ngIf="displaySortBadges$ | async">
<div>
<button
type="button"
class="badge-btn bg-primary-opacity-25 hover-bg-primary-opacity-75"
class="badge-btn bg-primary-opacity-50 hover-bg-primary-opacity-100"
(click)="clearSearchAndSort(SORT_BY_PARAMS.POPULARITY)"
>
<span translate="">datahub.header.popularRecords</span>
</button>
</div>
<div *ngFor="let preset of searchConfig?.SEARCH_PRESET">
<div>
<button
type="button"
class="badge-btn bg-primary-opacity-50 hover-bg-primary-opacity-100"
(click)="clearSearchAndFilterAndSort(preset)"
>
<span translate="">{{ preset['name'] }}</span>
</button>
</div>
</div>
</div>
</div>
<!--
Expand Down
99 changes: 75 additions & 24 deletions apps/datahub/src/app/home/home-header/home-header.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import {
RouterFacade,
ROUTER_ROUTE_SEARCH,
} from '@geonetwork-ui/feature/router'
import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search'
import {
FieldsService,
SearchFacade,
SearchService,
} from '@geonetwork-ui/feature/search'
import { SortByEnum } from '@geonetwork-ui/util/shared'
import { TranslateModule } from '@ngx-translate/core'
import { readFirst } from '@nrwl/angular/testing'
import { BehaviorSubject } from 'rxjs'
import { BehaviorSubject, of } from 'rxjs'
import { ROUTER_ROUTE_NEWS } from '../../router/constants'
import { HeaderBadgeButtonComponent } from '../header-badge-button/header-badge-button.component'
import { HomeHeaderComponent } from './home-header.component'
Expand All @@ -20,35 +24,56 @@ jest.mock('@geonetwork-ui/util/app-config', () => ({
getThemeConfig: () => ({
HEADER_BACKGROUND: 'red',
}),
getOptionalSearchConfig: () => ({
SEARCH_PRESET: [
{
sort: '-createDate',
name: 'sortCeatedDateAndOrg',
filters: { publisher: ['DREAL'] },
},
{
sort: '-createDate',
name: 'filterCarto',
filters: { q: 'Cartographie' },
},
],
}),
}))

const routerFacadeMock = {
goToMetadata: jest.fn(),
anySearch$: new BehaviorSubject('scot'),
currentRoute$: new BehaviorSubject({}),
class routerFacadeMock {
goToMetadata = jest.fn()
anySearch$ = new BehaviorSubject('scot')
currentRoute$ = new BehaviorSubject({})
}

const searchFacadeMock = {
setFavoritesOnly: jest.fn(),
setSortBy: jest.fn(),
class searchFacadeMock {
setFavoritesOnly = jest.fn()
setSortBy = jest.fn()
}

const searchServiceMock = {
updateSearchFilters: jest.fn(),
setSearch: jest.fn(),
setSortBy: jest.fn(),
setSortAndFilters: jest.fn(),
class searchServiceMock {
updateSearchFilters = jest.fn()
setSearch = jest.fn()
setSortBy = jest.fn()
setSortAndFilters = jest.fn()
}

class AuthServiceMock {
authReady = jest.fn(() => this._authSubject$)
_authSubject$ = new BehaviorSubject({})
}

class FieldsServiceMock {
buildFiltersFromFieldValues = jest.fn(() => of({ thisIs: 'a fake filter' }))
}

describe('HeaderComponent', () => {
let component: HomeHeaderComponent
let fixture: ComponentFixture<HomeHeaderComponent>
let authService: AuthService
let searchService: SearchService
let searchFacade: SearchFacade
let routerFacade: RouterFacade

beforeEach(async () => {
await TestBed.configureTestingModule({
Expand All @@ -58,23 +83,30 @@ describe('HeaderComponent', () => {
providers: [
{
provide: RouterFacade,
useValue: routerFacadeMock,
useClass: routerFacadeMock,
},
{
provide: SearchFacade,
useValue: searchFacadeMock,
useClass: searchFacadeMock,
},
{
provide: SearchService,
useValue: searchServiceMock,
useClass: searchServiceMock,
},
{
provide: AuthService,
useClass: AuthServiceMock,
},
{
provide: FieldsService,
useClass: FieldsServiceMock,
},
],
}).compileComponents()
authService = TestBed.inject(AuthService)
searchService = TestBed.inject(SearchService)
searchFacade = TestBed.inject(SearchFacade)
routerFacade = TestBed.inject(RouterFacade)
})

beforeEach(() => {
Expand Down Expand Up @@ -119,25 +151,25 @@ describe('HeaderComponent', () => {
component.listFavorites(true)
})
it('calls searchFacade setFavoritesOnly with correct value', () => {
expect(searchFacadeMock.setFavoritesOnly).toHaveBeenCalledWith(true)
expect(searchFacade.setFavoritesOnly).toHaveBeenCalledWith(true)
})
})
})
describe('sort badges', () => {
describe('navigate to search route', () => {
beforeEach(() => {
routerFacadeMock.currentRoute$.next({
;(routerFacade.currentRoute$ as any).next({
url: [{ path: ROUTER_ROUTE_SEARCH }],
})
})
it('does not display sort badges on search route', async () => {
it('displays sort badges on search route', async () => {
const displaySortBadges = await readFirst(component.displaySortBadges$)
expect(displaySortBadges).toEqual(false)
expect(displaySortBadges).toEqual(true)
})
})
describe('navigate to news route', () => {
beforeEach(() => {
routerFacadeMock.currentRoute$.next({
;(routerFacade.currentRoute$ as any).next({
url: [{ path: ROUTER_ROUTE_NEWS }],
})
})
Expand All @@ -154,7 +186,7 @@ describe('HeaderComponent', () => {
latestBadge.nativeElement.click()
})
it('resets filters and sort', () => {
expect(searchServiceMock.setSortAndFilters).toHaveBeenCalledWith(
expect(searchService.setSortAndFilters).toHaveBeenCalledWith(
{},
SortByEnum.CREATE_DATE
)
Expand All @@ -168,12 +200,31 @@ describe('HeaderComponent', () => {
mostPopularBadge.nativeElement.click()
})
it('resets filters and sort', () => {
expect(searchServiceMock.setSortAndFilters).toHaveBeenCalledWith(
expect(searchService.setSortAndFilters).toHaveBeenCalledWith(
{},
SortByEnum.POPULARITY
)
})
})

describe('given predefined search params', () => {
it('should render badges', () => {
const allBadges = fixture.debugElement.queryAll(By.css('.badge-btn'))
expect(allBadges.length).toBe(4)
})
beforeEach(() => {
const firstCustomBadge = fixture.debugElement.queryAll(
By.css('.badge-btn')
)[2]
firstCustomBadge.nativeElement.click()
})
it('should redirect correctly', () => {
expect(searchService.setSortAndFilters).toHaveBeenCalledWith(
{ thisIs: 'a fake filter' },
SortByEnum.CREATE_DATE
)
})
})
})
})
})
37 changes: 33 additions & 4 deletions apps/datahub/src/app/home/home-header/home-header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ import {
RouterFacade,
ROUTER_ROUTE_SEARCH,
} from '@geonetwork-ui/feature/router'
import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search'
import { getThemeConfig } from '@geonetwork-ui/util/app-config'
import {
FieldValues,
FieldsService,
SearchFacade,
SearchService,
} from '@geonetwork-ui/feature/search'
import {
getOptionalSearchConfig,
getThemeConfig,
SearchConfig,
SearchPreset,
} from '@geonetwork-ui/util/app-config'
import { MetadataRecord, SortByEnum } from '@geonetwork-ui/util/shared'
import { map } from 'rxjs/operators'
import { ROUTER_ROUTE_NEWS } from '../../router/constants'
import { lastValueFrom } from 'rxjs'

marker('datahub.header.myfavorites')
marker('datahub.header.lastRecords')
Expand All @@ -30,16 +41,22 @@ export class HomeHeaderComponent {

ROUTE_SEARCH = `${ROUTER_ROUTE_SEARCH}`
SORT_BY_PARAMS = SortByEnum
searchConfig: SearchConfig = getOptionalSearchConfig()

constructor(
public routerFacade: RouterFacade,
public searchFacade: SearchFacade,
private searchService: SearchService,
private authService: AuthService
private authService: AuthService,
private fieldsService: FieldsService
) {}

displaySortBadges$ = this.routerFacade.currentRoute$.pipe(
map((route) => route.url[0].path === ROUTER_ROUTE_NEWS)
map(
(route) =>
route.url[0].path === ROUTER_ROUTE_NEWS ||
route.url[0].path === ROUTER_ROUTE_SEARCH
)
)

isAuthenticated$ = this.authService
Expand All @@ -57,4 +74,16 @@ export class HomeHeaderComponent {
clearSearchAndSort(sort: SortByEnum): void {
this.searchService.setSortAndFilters({}, sort)
}

async clearSearchAndFilterAndSort(customSearchParameters: SearchPreset) {
const searchFilters = await lastValueFrom(
this.fieldsService.buildFiltersFromFieldValues(
customSearchParameters.filters
)
)
this.searchService.setSortAndFilters(
searchFilters,
customSearchParameters.sort as SortByEnum
)
}
}
27 changes: 26 additions & 1 deletion conf/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ proxy_path = ""
# - ${lang2}, ${lang3}: indicates if and where the current language should be part of the login URL in language 2 or 3 letter code
# Example to use the georchestra login page:
# login_url = "/cas/login?service=${current_url}"
# This optional URL should point to the static html page wc-embedder.html which allows to display a web component (like chart and table) via a permalink.
# This optional URL should point to the static html page wc-embedder.html which allows to display a web component (like chart and table) via a permalink.
# URLs can be indicated from the root of the same server starting with a "/" or as an external URL. Be conscious of potential CORS issues when using an external URL.
# The default location in the dockerized datahub app for example is "/datahub/wc-embedder.html".
# If the URL is not indicated, no permalinks will show up in the UI.
Expand Down Expand Up @@ -64,6 +64,31 @@ background_color = "#fdfbff"
# filter_geometry_url = "https://my.domain.org/assets/boundary.geojson"
# filter_geometry_data = '{ "coordinates": [...], "type": "Polygon" }'

# One or several search presets can be defined here; every search preset is composed of:
# - a name (which can be a translation key)
# - a sort criteria: either `createDate`, `userSavedCount` or `_score` (prepend with `-` for descending sort)
# - filters which can be expressed like so:
# [[search_preset]]
# filters.q = 'Full text search
# filters.publisher = ['Org 1', 'Org 2']
# filters.format = ['format 1', 'format 2']
# filters.documentStandard = ['iso19115-3.2018']
# filters.inspireKeyword = ['keyword 1', 'keyword 2']
# filters.topic = ['boundaries']
# filters.publicationYear = ['2023', '2022']
# filters.isSpatial = ['yes']
# filters.license = ['unknown']
#
# Search presets will be advertised to the user along the main search field.

# [[search_preset]]
# sort = "-createDate"
# name = 'filterByOrgs'
# filters.publisher = ['DREAL', 'atmo Hauts-de-France', 'blargz']
# [[search_preset]]
# name = 'Wind turbines'
# filters.q = 'wind'

### MAP SETTINGS

# The map section allows to customize how maps are configured.
Expand Down
4 changes: 2 additions & 2 deletions libs/feature/search/src/lib/utils/service/fields.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
SimpleSearchField,
TopicSearchField,
} from './fields'
import { combineLatest, forkJoin, Observable, of, takeLast } from 'rxjs'
import { map, mergeScan } from 'rxjs/operators'
import { forkJoin, Observable, of } from 'rxjs'
import { map } from 'rxjs/operators'

// key is the field name
export type FieldValues = Record<string, FieldValue[] | FieldValue>
Expand Down
1 change: 1 addition & 0 deletions libs/feature/search/src/lib/utils/service/fields.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ describe('search fields implementations', () => {
searchField.getFiltersForValues([
'Municipalité de Köniz',
'Office fédéral de la communication OFCOM',
'Non existent org',
])
)
})
Expand Down
4 changes: 3 additions & 1 deletion libs/feature/search/src/lib/utils/service/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ export class OrganizationSearchField implements AbstractSearchField {
getFiltersForValues(values: FieldValue[]): Observable<SearchFilters> {
return this.orgsService.organisations$.pipe(
map((orgs) =>
values.map((name) => orgs.find((org) => org.name === name))
values
.map((name) => orgs.find((org) => org.name === name))
.filter((org) => org !== undefined)
),
switchMap((selectedOrgs) =>
this.orgsService.getFiltersForOrgs(selectedOrgs)
Expand Down
Loading

0 comments on commit 6487b30

Please sign in to comment.