Skip to content

Commit

Permalink
Merge pull request #1015 from geonetwork/me-search-filters
Browse files Browse the repository at this point in the history
[ME]: Add search filters
  • Loading branch information
tkohr authored Oct 18, 2024
2 parents 2ade901 + c31e2cf commit 7b660c1
Show file tree
Hide file tree
Showing 24 changed files with 235 additions and 6 deletions.
32 changes: 32 additions & 0 deletions apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,38 @@ describe('dashboard (authenticated)', () => {
})
})
})
describe('search filters', () => {
describe('allRecords search filter', () => {
beforeEach(() => {
cy.visit('/catalog/search')
})
it('should filter the record list by editor (Barbara Roberts)', () => {
cy.get('md-editor-search-filters').find('gn-ui-button').first().click()
cy.get('.cdk-overlay-container')
.find('input[type="checkbox"]')
.eq(1)
.check()
cy.get('gn-ui-interactive-table')
.find('[data-cy="table-row"]')
.should('have.length', '5')
cy.get('gn-ui-results-table')
.find('[data-cy="ownerInfo"]')
.each(($ownerInfo) => {
cy.wrap($ownerInfo).invoke('text').should('eq', 'Barbara Roberts')
})
})
})
describe('myRecords search filters', () => {
beforeEach(() => {
cy.visit('/my-space/my-records')
})
it('should contain filter component with no search filter for now', () => {
cy.get('md-editor-search-filters')
.find('gn-ui-button')
.should('not.exist')
})
})
})
})

describe('when the user is not logged in', () => {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div
class="flex flex-row py-3 px-4 gap-4 shadow-md shadow-gray-300 border-[1px] border-gray-200 overflow-hidden rounded bg-white grow mx-[32px] my-[16px] text-sm"
>
<mat-icon class="material-symbols-outlined">filter_list</mat-icon>
<gn-ui-filter-dropdown
*ngFor="let filter of searchConfig; let i = index"
[fieldName]="filter.fieldName"
[title]="filter.title | translate"
[style.--gn-ui-button-height]="'32px'"
[style.--gn-ui-multiselect-counter-text-color]="'var(--color-primary)'"
[style.--gn-ui-multiselect-counter-background-color]="'white'"
></gn-ui-filter-dropdown>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SearchFiltersComponent } from './search-filters.component'
import { MockBuilder } from 'ng-mocks'
import { TranslateModule } from '@ngx-translate/core'

describe('SearchFiltersComponent', () => {
let component: SearchFiltersComponent
let fixture: ComponentFixture<SearchFiltersComponent>

beforeEach(() => {
return MockBuilder(SearchFiltersComponent)
})

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SearchFiltersComponent, TranslateModule.forRoot()],
}).compileComponents()
fixture = TestBed.createComponent(SearchFiltersComponent)
component = fixture.componentInstance
})

it('should create', () => {
expect(component).toBeTruthy()
})

describe('searchFields', () => {
it('should correctly read searchFields and create searchConfig', () => {
const searchFields = ['user', 'publisherOrg', 'format', 'isSpatial']
component.searchFields = searchFields
fixture.detectChanges()
expect(component.searchConfig).toEqual([
{ fieldName: 'user', title: 'search.filters.user' },
{ fieldName: 'publisherOrg', title: 'search.filters.publisherOrg' },
{ fieldName: 'format', title: 'search.filters.format' },
{ fieldName: 'isSpatial', title: 'search.filters.isSpatial' },
])
})
it('should read empty searchFields and create empty searchConfig', () => {
component.searchFields = []
fixture.detectChanges()
expect(component.searchConfig).toEqual([])
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, Input, OnInit } from '@angular/core'
import { CommonModule } from '@angular/common'
import { TranslateModule } from '@ngx-translate/core'
import { FeatureSearchModule } from '@geonetwork-ui/feature/search'
import { MatIconModule } from '@angular/material/icon'

@Component({
selector: 'md-editor-search-filters',
standalone: true,
imports: [CommonModule, TranslateModule, FeatureSearchModule, MatIconModule],
templateUrl: './search-filters.component.html',
styleUrls: ['./search-filters.component.css'],
})
export class SearchFiltersComponent implements OnInit {
@Input() searchFields: string[] = []
searchConfig: { fieldName: string; title: string }[]

ngOnInit(): void {
this.searchConfig = this.searchFields.map((filter) => ({
fieldName: filter,
title: `search.filters.${filter}`,
}))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ <h1 class="text-[16px] text-main font-title font-bold" translate>
<span translate>dashboard.createRecord</span>
</gn-ui-button>
</div>

<md-editor-search-filters
[searchFields]="searchFields"
></md-editor-search-filters>
<md-editor-records-list></md-editor-records-list>
</main>
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ImportRecordComponent } from '@geonetwork-ui/feature/editor'
import { RecordsListComponent } from '../records-list.component'
import { map } from 'rxjs/operators'
import { SearchHeaderComponent } from '../../dashboard/search-header/search-header.component'
import { SearchFiltersComponent } from '../../dashboard/search-filters/search-filters.component'

@Component({
selector: 'md-editor-all-records',
Expand All @@ -49,14 +50,15 @@ import { SearchHeaderComponent } from '../../dashboard/search-header/search-head
CdkConnectedOverlay,
RecordsListComponent,
SearchHeaderComponent,
SearchFiltersComponent,
],
})
export class AllRecordsComponent {
@ViewChild('importRecordButton', { read: ElementRef })
importRecordButton!: ElementRef
@ViewChild('template') template!: TemplateRef<any>
private overlayRef!: OverlayRef

searchFields = ['user']
searchText$: Observable<string | null> =
this.searchFacade.searchFilters$.pipe(
map((filters) => ('any' in filters ? (filters['any'] as string) : null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ <h1 class="text-[16px] text-main font-title font-bold" translate>
<span translate>dashboard.createRecord</span>
</gn-ui-button>
</div>

<md-editor-search-filters
[searchFields]="searchFields"
></md-editor-search-filters>
<md-editor-records-list></md-editor-records-list>
</main>
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { MatIconModule } from '@angular/material/icon'
import { ImportRecordComponent } from '@geonetwork-ui/feature/editor'
import { SearchHeaderComponent } from '../../dashboard/search-header/search-header.component'
import { map, Observable } from 'rxjs'
import { SearchFiltersComponent } from '../../dashboard/search-filters/search-filters.component'

@Component({
selector: 'md-editor-my-records',
Expand All @@ -45,14 +46,15 @@ import { map, Observable } from 'rxjs'
ImportRecordComponent,
FeatureSearchModule,
SearchHeaderComponent,
SearchFiltersComponent,
],
})
export class MyRecordsComponent implements OnInit {
@ViewChild('importRecordButton', { read: ElementRef })
private importRecordButton!: ElementRef
@ViewChild('template') template!: TemplateRef<any>
private overlayRef!: OverlayRef

searchFields = []
searchText$: Observable<string | null>

isImportMenuOpen = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ describe('FieldsService', () => {
'owner',
'producerOrg',
'publisherOrg',
'user',
])
})
})
Expand Down Expand Up @@ -184,6 +185,7 @@ describe('FieldsService', () => {
owner: [],
producerOrg: [],
publisherOrg: [],
user: [],
})
})
})
Expand Down
3 changes: 3 additions & 0 deletions libs/feature/search/src/lib/utils/service/fields.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
OwnerSearchField,
SimpleSearchField,
TranslatedSearchField,
UserSearchField,
} from './fields'
import { forkJoin, Observable, of } from 'rxjs'
import { map } from 'rxjs/operators'
Expand All @@ -33,6 +34,7 @@ marker('search.filters.topic')
marker('search.filters.contact')
marker('search.filters.producerOrg')
marker('search.filters.publisherOrg')
marker('search.filters.user')

@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -84,6 +86,7 @@ export class FieldsService {
'asc',
'key'
),
user: new UserSearchField(this.injector),
} as Record<string, AbstractSearchField>

get supportedFields() {
Expand Down
57 changes: 57 additions & 0 deletions libs/feature/search/src/lib/utils/service/fields.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
OrganizationSearchField,
SimpleSearchField,
MultilingualSearchField,
UserSearchField,
} from './fields'
import { TestBed } from '@angular/core/testing'
import { Injector } from '@angular/core'
Expand Down Expand Up @@ -98,6 +99,25 @@ class RecordsRepositoryMock {
],
},
})
if (aggName === 'userinfo.keyword')
return of({
'userinfo.keyword': {
buckets: [
{
term: 'admin|admin|admin|Administrator',
count: 10,
},
{
term: 'barbie|Roberts|Barbara|UserAdmin',
count: 5,
},
{
term: 'johndoe|Doe|John|Editor',
count: 1,
},
],
},
})
const buckets = [
{
term: 'First value',
Expand Down Expand Up @@ -666,4 +686,41 @@ describe('search fields implementations', () => {
})
})
})
describe('UserSearchField', () => {
beforeEach(() => {
searchField = new UserSearchField(injector)
})
describe('#getAvailableValues', () => {
let values
beforeEach(async () => {
values = await lastValueFrom(searchField.getAvailableValues())
})
it('calls aggregate with expected payload', () => {
expect(repository.aggregate).toHaveBeenCalledWith({
'userinfo.keyword': {
type: 'terms',
limit: 1000,
field: 'userinfo.keyword',
sort: ['asc', 'key'],
},
})
})
it('returns the available users, in expected format', () => {
expect(values).toEqual([
{
label: 'admin admin (10)',
value: 'admin|admin|admin|Administrator',
},
{
label: 'Barbara Roberts (5)',
value: 'barbie|Roberts|Barbara|UserAdmin',
},
{
label: 'John Doe (1)',
value: 'johndoe|Doe|John|Editor',
},
])
})
})
})
})
26 changes: 26 additions & 0 deletions libs/feature/search/src/lib/utils/service/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,29 @@ export class OwnerSearchField extends SimpleSearchField {
return of([])
}
}

export class UserSearchField extends SimpleSearchField {
constructor(injector: Injector) {
super('userinfo.keyword', injector, 'asc')
}

getAvailableValues(): Observable<FieldAvailableValue[]> {
return super.getAvailableValues().pipe(
map((values) =>
values.map((v) => ({
...v,
label: this.formatUserInfo(v.label),
}))
)
)
}

private formatUserInfo(userInfo: string | unknown): string {
const infos = (typeof userInfo === 'string' ? userInfo : '').split('|')
const count = infos[3].split(' ')[1]
if (infos && infos.length === 4) {
return `${infos[2]} ${infos[1]} ${count}`
}
return undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</div>
<div
*ngIf="hasSelectedChoices"
class="shrink-0 rounded-full text-primary bg-primary-opacity-25 text-white font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count"
class="gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count"
>
{{ selected.length }}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
</ng-template>
<ng-template #cell let-item>
<mat-icon class="material-symbols-outlined">person</mat-icon>
<span class="text-xs">{{ formatUserInfo(item.extras?.ownerInfo) }}</span>
<span data-cy="ownerInfo" class="text-xs">{{
formatUserInfo(item.extras?.ownerInfo)
}}</span>
</ng-template>
</gn-ui-interactive-table-column>

Expand Down
10 changes: 10 additions & 0 deletions tailwind.base.css
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@
border border-white focus:ring-4 focus:ring-gray-300;
}

/* DROPDOWN MULTISELECT CLASS */
.gn-ui-multiselect-counter {
--text-color: var(--gn-ui-multiselect-counter-text-color, white);
--background-color: var(
--gn-ui-multiselect-counter-background-color,
var(--color-primary-lightest)
);
@apply bg-[color:--background-color] text-[color:--text-color];
}

/* BADGE CLASS */
.gn-ui-badge {
--rounded: var(--gn-ui-badge-rounded, 0.25em);
Expand Down
1 change: 1 addition & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
"search.filters.topic": "Themen",
"search.filters.useSpatialFilter": "Zuerst Datensätze im Interessenbereich anzeigen",
"search.filters.useSpatialFilterHelp": "Wenn diese Option aktiviert ist, werden Datensätze im Bereich des Katalogs zuerst angezeigt. Datensätze außerhalb dieses Bereichs werden nicht angezeigt.",
"search.filters.user": "Editor",
"share.tab.permalink": "Teilen",
"share.tab.webComponent": "Integrieren",
"table.loading.data": "Daten werden geladen...",
Expand Down
1 change: 1 addition & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
"search.filters.topic": "Topics",
"search.filters.useSpatialFilter": "Show records in the area of interest first",
"search.filters.useSpatialFilterHelp": "When this is enabled, records within the catalog's area of interest are shown first; records outside of this area will not appear.",
"search.filters.user": "Editor",
"share.tab.permalink": "Share",
"share.tab.webComponent": "Integrate",
"table.loading.data": "Loading data...",
Expand Down
1 change: 1 addition & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
"search.filters.topic": "",
"search.filters.useSpatialFilter": "",
"search.filters.useSpatialFilterHelp": "",
"search.filters.user": "",
"share.tab.permalink": "",
"share.tab.webComponent": "",
"table.loading.data": "",
Expand Down
Loading

0 comments on commit 7b660c1

Please sign in to comment.