From fbff5fb102aeafd26ff9a574244a1a68103e9978 Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Fri, 20 Sep 2024 12:20:47 +0200 Subject: [PATCH 1/5] feat(my-records): add a myRecords search state --- apps/metadata-editor/src/app/app.routes.ts | 4 +- .../all-records/all-records.component.ts | 36 +++--------------- .../my-records-state-wrapper.component.html | 3 ++ .../my-records-state-wrapper.component.ts | 14 +++++++ .../my-records/my-records.component.html | 16 +------- .../my-records/my-records.component.ts | 37 ++----------------- .../results-table-container.component.html | 3 +- .../results-table-container.component.ts | 3 +- 8 files changed, 33 insertions(+), 83 deletions(-) create mode 100644 apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.html create mode 100644 apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.ts diff --git a/apps/metadata-editor/src/app/app.routes.ts b/apps/metadata-editor/src/app/app.routes.ts index 0a9e7df065..f2eac576b8 100644 --- a/apps/metadata-editor/src/app/app.routes.ts +++ b/apps/metadata-editor/src/app/app.routes.ts @@ -3,13 +3,13 @@ import { DashboardPageComponent } from './dashboard/dashboard-page.component' import { SignInPageComponent } from './sign-in/sign-in-page.component' import { EditPageComponent } from './edit/edit-page.component' import { EditRecordResolver } from './edit-record.resolver' -import { MyRecordsComponent } from './records/my-records/my-records.component' import { MyDraftComponent } from './records/my-draft/my-draft.component' import { TemplatesComponent } from './records/templates/templates.component' import { MyOrgUsersComponent } from './my-org-users/my-org-users.component' import { NewRecordResolver } from './new-record.resolver' import { DuplicateRecordResolver } from './duplicate-record.resolver' import { AllRecordsComponent } from './records/all-records/all-records.component' +import { MyRecordsStateWrapperComponent } from './records/my-records/my-records-state-wrapper.component' export const appRoutes: Route[] = [ { path: '', redirectTo: 'catalog/search', pathMatch: 'prefix' }, @@ -60,7 +60,7 @@ export const appRoutes: Route[] = [ { path: 'my-records', title: 'My Records', - component: MyRecordsComponent, + component: MyRecordsStateWrapperComponent, pathMatch: 'prefix', }, { diff --git a/apps/metadata-editor/src/app/records/all-records/all-records.component.ts b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts index cd1412b1e2..2d0f43cfb7 100644 --- a/apps/metadata-editor/src/app/records/all-records/all-records.component.ts +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts @@ -16,7 +16,7 @@ import { import { TranslateModule } from '@ngx-translate/core' import { ActivatedRoute, Router } from '@angular/router' import { RecordsCountComponent } from '../records-count/records-count.component' -import { Observable, of } from 'rxjs' +import { Observable } from 'rxjs' import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { MatIconModule } from '@angular/material/icon' @@ -68,7 +68,10 @@ export class AllRecordsComponent implements OnInit { @ViewChild('template') template!: TemplateRef private overlayRef!: OverlayRef - searchText$: Observable = of(null) + searchText$: Observable = + this.searchFacade.searchFilters$.pipe( + map((filters) => ('any' in filters ? (filters['any'] as string) : null)) + ) isImportMenuOpen = false @@ -83,34 +86,7 @@ export class AllRecordsComponent implements OnInit { ) {} ngOnInit(): void { - this.searchText$ = this.searchFacade.searchFilters$.pipe( - map((filters) => ('any' in filters ? (filters['any'] as string) : null)) - ) - - this.searchFacade.resetSearch() - - const searchTerms = this.activedRoute.snapshot.queryParams['q'] ?? '' - - if (searchTerms) { - this.searchFacade.setFilters({ any: searchTerms }) - } - - let sort = (this.activedRoute.snapshot.queryParams['_sort'] as string) ?? '' - - if (sort) { - let ascDesc = '' - - if (sort?.charAt(0) === '-') { - ascDesc = 'desc' - sort = sort.slice(1, sort.length) - } else { - ascDesc = 'asc' - } - this.searchFacade.setSortBy([ascDesc as 'asc' | 'desc', sort]) - } - - this.searchFacade.setPageSize(15) - this.searchFacade.setConfigRequestFields(allSearchFields) + this.searchFacade.setConfigRequestFields(allSearchFields).setPageSize(15) } createRecord() { diff --git a/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.html b/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.html new file mode 100644 index 0000000000..0091906b78 --- /dev/null +++ b/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.ts b/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.ts new file mode 100644 index 0000000000..4add9d4b58 --- /dev/null +++ b/apps/metadata-editor/src/app/records/my-records/my-records-state-wrapper.component.ts @@ -0,0 +1,14 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { MyRecordsComponent } from './my-records.component' +import { FeatureSearchModule } from '@geonetwork-ui/feature/search' +import { CommonModule } from '@angular/common' + +@Component({ + selector: 'md-editor-my-records-state-wrapper', + templateUrl: './my-records-state-wrapper.component.html', + styles: [], + standalone: true, + imports: [CommonModule, FeatureSearchModule, MyRecordsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MyRecordsStateWrapperComponent {} diff --git a/apps/metadata-editor/src/app/records/my-records/my-records.component.html b/apps/metadata-editor/src/app/records/my-records/my-records.component.html index 577f01a0ee..a8456fafd4 100644 --- a/apps/metadata-editor/src/app/records/my-records/my-records.component.html +++ b/apps/metadata-editor/src/app/records/my-records/my-records.component.html @@ -1,25 +1,13 @@
- -

- dashboard.records.search -

-
- -
-
- +

dashboard.records.myRecords

-
+
private overlayRef!: OverlayRef - searchText$: Observable = - this.searchFacade.searchFilters$.pipe( - map((filters) => ('any' in filters ? (filters['any'] as string) : null)) - ) - isImportMenuOpen = false constructor( private router: Router, private activedRoute: ActivatedRoute, - private searchFacade: SearchFacade, - public searchService: SearchService, + protected searchFacade: SearchFacade, private platformService: PlatformServiceInterface, private fieldsService: FieldsService, private overlay: Overlay, @@ -79,30 +71,7 @@ export class MyRecordsComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - this.searchFacade.resetSearch() - - const searchTerms = this.activedRoute.snapshot.queryParams['q'] ?? '' - - if (searchTerms) { - this.searchFacade.setFilters({ any: searchTerms }) - } - - let sort = (this.activedRoute.snapshot.queryParams['_sort'] as string) ?? '' - - if (sort) { - let ascDesc = '' - - if (sort?.charAt(0) === '-') { - ascDesc = 'desc' - sort = sort.slice(1, sort.length) - } else { - ascDesc = 'asc' - } - this.searchFacade.setSortBy([ascDesc as 'asc' | 'desc', sort]) - } - - this.searchFacade.setPageSize(15) - this.searchFacade.setConfigRequestFields(allSearchFields) + this.searchFacade.setConfigRequestFields(allSearchFields).setPageSize(15) this.sub = this.platformService.getMe().subscribe((user) => { this.ownerId = user.id diff --git a/libs/feature/search/src/lib/results-table/results-table-container.component.html b/libs/feature/search/src/lib/results-table/results-table-container.component.html index 40f4569d7f..f951cd8aea 100644 --- a/libs/feature/search/src/lib/results-table/results-table-container.component.html +++ b/libs/feature/search/src/lib/results-table/results-table-container.component.html @@ -1,5 +1,6 @@ Date: Fri, 20 Sep 2024 14:12:29 +0200 Subject: [PATCH 2/5] feat(dashboard-menu): reset main search when navigating to my records and my drafts --- .../dashboard-menu/dashboard-menu.component.html | 2 ++ .../dashboard-menu/dashboard-menu.component.spec.ts | 13 ++++++++++++- .../dashboard-menu/dashboard-menu.component.ts | 10 +++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html index 7ded10a5ee..389d27da2a 100644 --- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html +++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html @@ -56,6 +56,7 @@ { let component: DashboardMenuComponent let fixture: ComponentFixture let recordsRepository: RecordsRepositoryInterface + let searchFacade: SearchFacade beforeEach(() => { return MockBuilder(DashboardMenuComponent) @@ -18,9 +20,12 @@ describe('DashboardMenuComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DashboardMenuComponent, TranslateModule.forRoot()], - providers: [MockProviders(ActivatedRoute, RecordsRepositoryInterface)], + providers: [ + MockProviders(ActivatedRoute, RecordsRepositoryInterface, SearchFacade), + ], }).compileComponents() recordsRepository = TestBed.inject(RecordsRepositoryInterface) + searchFacade = TestBed.inject(SearchFacade) fixture = TestBed.createComponent(DashboardMenuComponent) component = fixture.componentInstance fixture.detectChanges() @@ -45,4 +50,10 @@ describe('DashboardMenuComponent', () => { // Assert that draftsCount$ behaves as expected expect(component.draftsCount$).toBeObservable(expected) }) + + it('should reset filters in main search', () => { + searchFacade.setFilters = jest.fn() + component.resetMainSearch() + expect(searchFacade.setFilters).toHaveBeenCalledWith({}) + }) }) diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts index e602f6d713..a6a3454881 100644 --- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts +++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts @@ -6,6 +6,7 @@ import { TranslateModule } from '@ngx-translate/core' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { map, startWith, switchMap } from 'rxjs/operators' import { BadgeComponent } from '@geonetwork-ui/ui/inputs' +import { SearchFacade } from '@geonetwork-ui/feature/search' @Component({ selector: 'md-editor-dashboard-menu', @@ -29,5 +30,12 @@ export class DashboardMenuComponent { ) activeLink = false - constructor(private recordsRepository: RecordsRepositoryInterface) {} + constructor( + private recordsRepository: RecordsRepositoryInterface, + private searchFacade: SearchFacade + ) {} + + resetMainSearch() { + this.searchFacade.setFilters({}) + } } From 95e149dcdd70ef21476cd29bfd4949cfa778f249 Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Fri, 20 Sep 2024 15:33:43 +0200 Subject: [PATCH 3/5] test(my-records): adapt unit tests --- .../records/all-records/all-records.component.ts | 3 ++- .../my-records/my-records.component.spec.ts | 14 ++++++++++---- .../app/records/my-records/my-records.component.ts | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/metadata-editor/src/app/records/all-records/all-records.component.ts b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts index 2d0f43cfb7..8c0634473b 100644 --- a/apps/metadata-editor/src/app/records/all-records/all-records.component.ts +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts @@ -86,7 +86,8 @@ export class AllRecordsComponent implements OnInit { ) {} ngOnInit(): void { - this.searchFacade.setConfigRequestFields(allSearchFields).setPageSize(15) + this.searchFacade.setConfigRequestFields(allSearchFields) + this.searchFacade.setPageSize(15) } createRecord() { diff --git a/apps/metadata-editor/src/app/records/my-records/my-records.component.spec.ts b/apps/metadata-editor/src/app/records/my-records/my-records.component.spec.ts index 64ddf17f6c..0a24ee9ad5 100644 --- a/apps/metadata-editor/src/app/records/my-records/my-records.component.spec.ts +++ b/apps/metadata-editor/src/app/records/my-records/my-records.component.spec.ts @@ -13,6 +13,7 @@ import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform. import { MockBuilder, MockInstance, MockProviders } from 'ng-mocks' import { ActivatedRoute, Router } from '@angular/router' import { TranslateModule } from '@ngx-translate/core' +import { allSearchFields } from '../all-records/all-records.component' describe('MyRecordsComponent', () => { MockInstance.scope() @@ -106,11 +107,16 @@ describe('MyRecordsComponent', () => { expect(component).toBeTruthy() }) - describe('filters', () => { - it('clears filters on init', () => { - expect(searchFacade.resetSearch).toHaveBeenCalled() + describe('filters on init', () => { + it('sets search fields', () => { + expect(searchFacade.setConfigRequestFields).toHaveBeenCalledWith( + allSearchFields + ) + }) + it('sets page size', () => { + expect(searchFacade.setPageSize).toHaveBeenCalledWith(15) }) - it('Update filters on init', () => { + it('updates filters with owner', () => { expect(searchFacade.updateFilters).toHaveBeenCalledWith({ owner: user.id, }) diff --git a/apps/metadata-editor/src/app/records/my-records/my-records.component.ts b/apps/metadata-editor/src/app/records/my-records/my-records.component.ts index 0262e9e5db..028dcf33f9 100644 --- a/apps/metadata-editor/src/app/records/my-records/my-records.component.ts +++ b/apps/metadata-editor/src/app/records/my-records/my-records.component.ts @@ -71,7 +71,8 @@ export class MyRecordsComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - this.searchFacade.setConfigRequestFields(allSearchFields).setPageSize(15) + this.searchFacade.setConfigRequestFields(allSearchFields) + this.searchFacade.setPageSize(15) this.sub = this.platformService.getMe().subscribe((user) => { this.ownerId = user.id From 9fdfb6619d7f89d908342a7b7245d36fdf2262e5 Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Fri, 20 Sep 2024 18:03:27 +0200 Subject: [PATCH 4/5] test(e2e): add e2e tests --- .../src/e2e/dashboard.cy.ts | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index 654849977c..7105e28ae5 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -214,17 +214,35 @@ describe('dashboard', () => { }) }) describe('navigation', () => { + function checkDashboardFiltered() { + cy.get('gn-ui-autocomplete').type('Mat') + cy.get('mat-option').first().click() + cy.get('gn-ui-interactive-table') + .find('[data-cy="table-row"]') + .should('have.length', '1') + } beforeEach(() => { cy.login('admin', 'admin', false) cy.visit('/catalog/search') }) describe('search input', () => { it('should filter the dashboard based on the search input', () => { + checkDashboardFiltered() + }) + // TODO remove skip when handleItemSelection of autocomplete is handled correctly + it.skip('should navigate to list of all records and filter the dashboard based on the search input when on different page', () => { + cy.visit('/my-space/my-records') + checkDashboardFiltered() + }) + it('should clear the search input when navigating to my records', () => { cy.get('gn-ui-autocomplete').type('Mat') - cy.get('mat-option').first().click() - cy.get('gn-ui-interactive-table') - .find('[data-cy="table-row"]') - .should('have.length', '1') + cy.get('md-editor-dashboard-menu').find('a').eq(5).click() + cy.get('gn-ui-autocomplete').should('have.value', '') + }) + it('should clear the search input when navigating to my drafts', () => { + cy.get('gn-ui-autocomplete').type('Mat') + cy.get('md-editor-dashboard-menu').find('a').eq(6).click() + cy.get('gn-ui-autocomplete').should('have.value', '') }) }) describe('my records', () => { @@ -236,6 +254,31 @@ describe('dashboard', () => { .next() .should('contain', 'admin admin') }) + it('should display the correct amount of records', () => { + cy.get('md-editor-dashboard-menu').find('a').eq(5).click() + cy.get('gn-ui-results-table') + .find('[data-cy="table-row"]') + .should('have.length', '10') + }) + it('should sort the records by title', () => { + cy.get('md-editor-dashboard-menu').find('a').eq(5).click() + cy.get('gn-ui-results-table') + .find('[data-cy="table-row"]') + .first() + .invoke('text') + .then((firstRecord) => { + console.log(firstRecord) + cy.get('gn-ui-results-table') + .find('.table-header-cell') + .eq(1) + .click() + cy.get('gn-ui-results-table') + .find('[data-cy="table-row"]') + .first() + .invoke('text') + .should('not.eq', firstRecord) + }) + }) }) }) }) From 8995729cf8c30b0490db8e3acdfebed221b85d62 Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Mon, 23 Sep 2024 10:14:17 +0200 Subject: [PATCH 5/5] fix(search-header): update filters on autocomplete selection --- apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts | 3 +-- .../search-header/search-header.component.html | 1 + .../search-header/search-header.component.spec.ts | 10 +++++++++- .../search-header/search-header.component.ts | 13 +++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index 7105e28ae5..f6f6c161c5 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -229,8 +229,7 @@ describe('dashboard', () => { it('should filter the dashboard based on the search input', () => { checkDashboardFiltered() }) - // TODO remove skip when handleItemSelection of autocomplete is handled correctly - it.skip('should navigate to list of all records and filter the dashboard based on the search input when on different page', () => { + it('should navigate to list of all records and filter the dashboard based on the search input when on different page', () => { cy.visit('/my-space/my-records') checkDashboardFiltered() }) diff --git a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.html b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.html index fffb542060..ea1efc582a 100644 --- a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.html +++ b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.html @@ -1,6 +1,7 @@
diff --git a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.spec.ts b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.spec.ts index 858e05b7be..1abf932325 100644 --- a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.spec.ts +++ b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.spec.ts @@ -7,9 +7,9 @@ import { barbieUserFixture } from '@geonetwork-ui/common/fixtures' import { StoreModule } from '@ngrx/store' import { EffectsModule } from '@ngrx/effects' import { TranslateModule } from '@ngx-translate/core' -import { TRANSLATE_DEFAULT_CONFIG } from '@geonetwork-ui/util/i18n' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { AvatarServiceInterface } from '@geonetwork-ui/api/repository' +import { SearchService } from '@geonetwork-ui/feature/search' class AvatarServiceInterfaceMock { getPlaceholder = () => of('http://placeholder.com') @@ -21,6 +21,10 @@ class PlatformServiceMock { getMe = jest.fn(() => me$) } +class SearchServiceMock { + updateFilters = jest.fn() +} + describe('SearchHeaderComponent', () => { let component: SearchHeaderComponent let fixture: ComponentFixture @@ -42,6 +46,10 @@ describe('SearchHeaderComponent', () => { provide: PlatformServiceInterface, useClass: PlatformServiceMock, }, + { + provide: SearchService, + useClass: SearchServiceMock, + }, ], }) .overrideComponent(SearchHeaderComponent, { diff --git a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.ts b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.ts index cd81a297b1..1c3490f5c5 100644 --- a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.ts +++ b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.ts @@ -2,11 +2,15 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { LetDirective } from '@ngrx/component' -import { FeatureSearchModule } from '@geonetwork-ui/feature/search' +import { + FeatureSearchModule, + SearchService, +} from '@geonetwork-ui/feature/search' import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { AvatarServiceInterface } from '@geonetwork-ui/api/repository' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { TranslateModule } from '@ngx-translate/core' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' @Component({ selector: 'md-editor-search-header', @@ -29,6 +33,11 @@ export class SearchHeaderComponent { constructor( public platformService: PlatformServiceInterface, - private avatarService: AvatarServiceInterface + private avatarService: AvatarServiceInterface, + private searchService: SearchService ) {} + + handleItemSelection(item: CatalogRecord) { + this.searchService.updateFilters({ any: item.title }) + } }