From 12b0e3c45a9bdd8f5a35064e8d493cae6e142e91 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 16 Sep 2024 15:44:05 +0200 Subject: [PATCH] wip --- apps/metadata-editor/src/app/app.routes.ts | 22 +-- .../dashboard-menu.component.html | 10 +- .../dashboard/dashboard-page.component.html | 2 +- .../search-header/search-header.component.css | 4 +- .../search-header.component.html | 4 +- .../all-records.component.css} | 0 .../all-records.component.html} | 20 +- .../all-records.component.spec.ts} | 185 ++++++++++-------- .../all-records.component.ts} | 63 ++++-- .../my-library/my-library.component.html | 2 - .../my-org-records.component.html | 7 - .../my-org-records.component.spec.ts | 147 -------------- .../my-org-records.component.ts | 54 ----- .../my-records/my-records.component.html | 78 +++++++- .../my-records/my-records.component.spec.ts | 138 ++++++------- .../my-records/my-records.component.ts | 152 ++++++++++++-- .../app/records/records-list.component.html | 83 ++------ .../src/app/records/records-list.component.ts | 33 +--- .../templates.component.css} | 0 .../templates.component.html} | 0 .../templates.component.spec.ts} | 10 +- .../templates.component.ts} | 8 +- .../import-record.component.spec.ts | 8 +- .../results-table-container.component.html | 2 + .../results-table-container.component.ts | 3 + .../inputs/src/lib/badge/badge.component.html | 3 +- .../interactive-table.component.css | 2 +- .../interactive-table.component.html | 4 +- .../results-table.component.html | 23 ++- tailwind.base.css | 10 +- translations/de.json | 2 + translations/en.json | 2 + translations/es.json | 2 + translations/fr.json | 2 + translations/it.json | 2 + translations/nl.json | 2 + translations/pt.json | 2 + translations/sk.json | 2 + 38 files changed, 523 insertions(+), 570 deletions(-) rename apps/metadata-editor/src/app/records/{my-library/my-library.component.css => all-records/all-records.component.css} (100%) rename apps/metadata-editor/src/app/records/{search-records/search-records-list.component.html => all-records/all-records.component.html} (74%) rename apps/metadata-editor/src/app/records/{search-records/search-records-list.component.spec.ts => all-records/all-records.component.spec.ts} (52%) rename apps/metadata-editor/src/app/records/{search-records/search-records-list.component.ts => all-records/all-records.component.ts} (71%) delete mode 100644 apps/metadata-editor/src/app/records/my-library/my-library.component.html delete mode 100644 apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.html delete mode 100644 apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.spec.ts delete mode 100644 apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts rename apps/metadata-editor/src/app/records/{my-org-records/my-org-records.component.css => templates/templates.component.css} (100%) rename apps/metadata-editor/src/app/records/{search-records/search-records-list.component.css => templates/templates.component.html} (100%) rename apps/metadata-editor/src/app/records/{my-library/my-library.component.spec.ts => templates/templates.component.spec.ts} (83%) rename apps/metadata-editor/src/app/records/{my-library/my-library.component.ts => templates/templates.component.ts} (74%) diff --git a/apps/metadata-editor/src/app/app.routes.ts b/apps/metadata-editor/src/app/app.routes.ts index d8c5da1256..0a9e7df065 100644 --- a/apps/metadata-editor/src/app/app.routes.ts +++ b/apps/metadata-editor/src/app/app.routes.ts @@ -5,12 +5,11 @@ 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 { MyLibraryComponent } from './records/my-library/my-library.component' -import { SearchRecordsComponent } from './records/search-records/search-records-list.component' +import { TemplatesComponent } from './records/templates/templates.component' import { MyOrgUsersComponent } from './my-org-users/my-org-users.component' -import { MyOrgRecordsComponent } from './records/my-org-records/my-org-records.component' import { NewRecordResolver } from './new-record.resolver' import { DuplicateRecordResolver } from './duplicate-record.resolver' +import { AllRecordsComponent } from './records/all-records/all-records.component' export const appRoutes: Route[] = [ { path: '', redirectTo: 'catalog/search', pathMatch: 'prefix' }, @@ -26,35 +25,30 @@ export const appRoutes: Route[] = [ }, { path: 'discussion', - component: SearchRecordsComponent, + component: AllRecordsComponent, pathMatch: 'prefix', }, { path: 'calendar', - component: SearchRecordsComponent, + component: AllRecordsComponent, pathMatch: 'prefix', }, { path: 'contacts', - component: SearchRecordsComponent, + component: AllRecordsComponent, pathMatch: 'prefix', }, { path: 'thesaurus', - component: SearchRecordsComponent, + component: AllRecordsComponent, pathMatch: 'prefix', }, { path: 'search', title: 'Search Records', - component: SearchRecordsComponent, + component: AllRecordsComponent, pathMatch: 'prefix', }, - { - path: 'my-org', - title: 'My Organisation', - component: MyOrgRecordsComponent, - }, ], }, { @@ -78,7 +72,7 @@ export const appRoutes: Route[] = [ { path: 'templates', title: 'Templates', - component: MyLibraryComponent, + component: TemplatesComponent, pathMatch: 'prefix', }, ], 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 b57c19c048..d92a3a4149 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 @@ -67,11 +67,11 @@ > edit_note dashboard.records.myDraft - {{ draftsCount$ | async }} + + {{ + draftsCount + }} +
-
+
diff --git a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.css b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.css index 03bcc57c34..542d168034 100644 --- a/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.css +++ b/apps/metadata-editor/src/app/dashboard/search-header/search-header.component.css @@ -1,8 +1,8 @@ :host ::ng-deep gn-ui-autocomplete input { - @apply bg-blue-50 rounded-3xl shadow-none !py-2 !px-8 !pl-14 hover:shadow-none focus:outline-4 focus:outline-offset-1 focus:outline-blue-100 focus:outline-dotted; + @apply rounded-3xl shadow-none !py-2 !px-8 !pl-14 hover:shadow-none; } :host ::ng-deep gn-ui-autocomplete button { - @apply bg-blue-50 hover:bg-blue-50 border-none shadow-none hover:shadow-none text-gray-500 hover:text-gray-600 left-0 right-auto rounded-3xl; + @apply border-none shadow-none hover:shadow-none text-gray-500 hover:text-gray-600 left-0 right-auto rounded-3xl; } ::ng-deep .mdc-menu-surface.mat-mdc-autocomplete-panel { 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 9b0edbaf48..a74a3577c4 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,8 @@
- +
-
- - -
-
- -
-
-
+ diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts b/apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts similarity index 52% rename from apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts rename to apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts index d39c0a6771..4080607afb 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts @@ -1,23 +1,28 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' -import { SearchRecordsComponent } from './search-records-list.component' import { + FieldsService, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' +import { + ChangeDetectionStrategy, Component, - CUSTOM_ELEMENTS_SCHEMA, EventEmitter, - importProvidersFrom, Input, Output, } from '@angular/core' import { TranslateModule } from '@ngx-translate/core' -import { BehaviorSubject } from 'rxjs' +import { BehaviorSubject, of } from 'rxjs' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' -import { CommonModule } from '@angular/common' -import { MatIconModule } from '@angular/material/icon' -import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { By } from '@angular/platform-browser' -import { datasetRecordsFixture } from '@geonetwork-ui/common/fixtures' -import { Router } from '@angular/router' +import { barbieUserFixture } from '@geonetwork-ui/common/fixtures' +import { ActivatedRoute, Router } from '@angular/router' +import { AllRecordsComponent } from './all-records.component' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { MockBuilder, MockInstance, MockProviders } from 'ng-mocks' +import { EditorRouterService } from '../../router.service' +import { Overlay } from '@angular/cdk/overlay' +import { RecordsListComponent } from '../records-list.component' const results = [{ md: true }] const currentPage = 5 @@ -77,50 +82,107 @@ class RouterMock { navigate = jest.fn(() => Promise.resolve(true)) } -describe('SearchRecordsComponent', () => { - let component: SearchRecordsComponent - let fixture: ComponentFixture +describe('AllRecordsComponent', () => { + MockInstance.scope() + + const searchFilters = new BehaviorSubject({ + any: 'hello world', + }) + + let component: AllRecordsComponent + let fixture: ComponentFixture + let router: Router - let searchService: SearchService let searchFacade: SearchFacade + let platformService: PlatformServiceInterface + let fieldsService: FieldsService + + beforeEach(() => { + return MockBuilder(AllRecordsComponent) + }) beforeEach(() => { TestBed.configureTestingModule({ - schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [TranslateModule.forRoot()], providers: [ - importProvidersFrom(TranslateModule.forRoot()), - { - provide: SearchFacade, - useClass: SearchFacadeMock, - }, - { - provide: Router, - useClass: RouterMock, - }, - { - provide: SearchService, - useClass: SearchServiceMock, - }, + MockProviders( + FieldsService, + SearchFacade, + PlatformServiceInterface, + EditorRouterService, + Overlay, + ActivatedRoute, + SearchService + ), ], - }).overrideComponent(SearchRecordsComponent, { + }).overrideComponent(AllRecordsComponent, { set: { - imports: [ - CommonModule, - TranslateModule, - MatIconModule, - ResultsTableContainerComponent, - PaginationButtonsComponent, - UiInputsModule, - RecordsCountComponent, - ], + changeDetection: ChangeDetectionStrategy.Default, }, }) - fixture = TestBed.createComponent(SearchRecordsComponent) + + MockInstance( + SearchFacade, + 'searchFilters$', + jest.fn(), + 'get' + ).mockReturnValue(searchFilters) + + MockInstance(ActivatedRoute, 'snapshot', jest.fn(), 'get').mockReturnValue({ + paramMap: new Map([['paramId', 'paramValue']]), + queryParams: new Map([['paramId', 'paramValue']]), + }) + + // const flexibleConnectedPositionStrategyMock = MockInstance( + // FlexibleConnectedPositionStrategy, + // 'withPositions', + // jest.fn(), + // 'get' + // ).mockReturnValue({}) + // const overlayPositionMock = MockInstance( + // OverlayPositionBuilder, + // 'flexibleConnectedTo', + // jest.fn(), + // 'get' + // ).mockReturnValue(flexibleConnectedPositionStrategyMock) + // MockInstance(Overlay, 'position', jest.fn(), 'get').mockReturnValue( + // overlayPositionMock + // ) + + fixture = TestBed.createComponent(AllRecordsComponent) + router = TestBed.inject(Router) - searchService = TestBed.inject(SearchService) searchFacade = TestBed.inject(SearchFacade) + platformService = TestBed.inject(PlatformServiceInterface) + fieldsService = TestBed.inject(FieldsService) + + router.navigate = jest.fn().mockReturnValue(Promise.resolve(true)) + + platformService.getMe = jest.fn( + () => new BehaviorSubject(barbieUserFixture()) + ) + + fieldsService.buildFiltersFromFieldValues = jest.fn((fieldValues) => + of( + Object.keys(fieldValues).reduce( + (_, curr) => ({ + [curr]: fieldValues[curr], + }), + {} + ) + ) + ) + + // searchFacade.searchFilters$ = new BehaviorSubject({ any: 'scot' }) + searchFacade.resetSearch = jest.fn(() => this) + searchFacade.updateFilters = jest.fn(() => this) + searchFacade.setFilters = jest.fn(() => this) + searchFacade.setSortBy = jest.fn(() => this) + searchFacade.setPageSize = jest.fn(() => this) + searchFacade.setConfigRequestFields = jest.fn(() => this) component = fixture.componentInstance + fixture.detectChanges() }) @@ -177,59 +239,26 @@ describe('SearchRecordsComponent', () => { }) describe('when search results', () => { - let table, pagination + let results: any beforeEach(() => { - table = fixture.debugElement.query( - By.directive(ResultsTableContainerComponent) - ).componentInstance - pagination = fixture.debugElement.query( - By.directive(PaginationButtonsComponent) + results = fixture.debugElement.query( + By.directive(RecordsListComponent) ).componentInstance }) - it('displays record table', () => { - expect(table).toBeTruthy() - }) - it('displays pagination', () => { - expect(pagination).toBeTruthy() - expect(pagination.currentPage).toEqual(currentPage) - expect(pagination.totalPages).toEqual(totalPages) + it('displays record list', () => { + expect(results).toBeTruthy() }) describe('when click on a record', () => { - const uniqueIdentifier = 123 - const singleRecord = { - ...datasetRecordsFixture()[0], - uniqueIdentifier, - } - beforeEach(() => { - table.recordClick.emit(singleRecord) - }) it('routes to record edition', () => { expect(router.navigate).toHaveBeenCalledWith(['/edit', 123]) }) }) describe('when asking for record duplication', () => { - const uniqueIdentifier = 123 - const singleRecord = { - ...datasetRecordsFixture()[0], - uniqueIdentifier, - } - beforeEach(() => { - table.duplicateRecord.emit(singleRecord) - }) it('routes to record duplication', () => { expect(router.navigate).toHaveBeenCalledWith(['/duplicate', 123]) }) }) - - describe('when click on pagination', () => { - beforeEach(() => { - pagination.newCurrentPageEvent.emit(3) - }) - it('paginates', () => { - expect(searchService.setPage).toHaveBeenCalledWith(3) - }) - }) }) }) diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts similarity index 71% rename from apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts rename to apps/metadata-editor/src/app/records/all-records/all-records.component.ts index be862df39e..52e8e196a8 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.ts @@ -3,6 +3,7 @@ import { ChangeDetectorRef, Component, ElementRef, + OnInit, TemplateRef, ViewChild, ViewContainerRef, @@ -14,8 +15,7 @@ import { } from '@geonetwork-ui/feature/search' import { TranslateModule } from '@ngx-translate/core' import { map } from 'rxjs/operators' -import { Router } from '@angular/router' -import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' +import { ActivatedRoute, Router } from '@angular/router' import { RecordsCountComponent } from '../records-count/records-count.component' import { Observable } from 'rxjs' import { UiElementsModule } from '@geonetwork-ui/ui/elements' @@ -29,11 +29,24 @@ import { } from '@angular/cdk/overlay' import { TemplatePortal } from '@angular/cdk/portal' import { ImportRecordComponent } from '@geonetwork-ui/feature/editor' +import { RecordsListComponent } from '../records-list.component' + +export const allSearchFields = [ + 'uuid', + 'resourceTitleObject', + 'createDate', + 'changeDate', + 'userinfo', + 'cl_status', + 'isPublishedToAll', + 'link', + 'owner', +] @Component({ - selector: 'md-editor-search-records-list', - templateUrl: './search-records-list.component.html', - styleUrls: ['./search-records-list.component.css'], + selector: 'md-editor-all-records', + templateUrl: './all-records.component.html', + styleUrls: ['./all-records.component.css'], standalone: true, imports: [ CommonModule, @@ -46,9 +59,10 @@ import { ImportRecordComponent } from '@geonetwork-ui/feature/editor' ImportRecordComponent, CdkOverlayOrigin, CdkConnectedOverlay, + RecordsListComponent, ], }) -export class SearchRecordsComponent { +export class AllRecordsComponent implements OnInit { @ViewChild('importRecordButton', { read: ElementRef }) private importRecordButton!: ElementRef @ViewChild('template') template!: TemplateRef @@ -63,26 +77,39 @@ export class SearchRecordsComponent { constructor( private router: Router, + private activedRoute: ActivatedRoute, public searchFacade: SearchFacade, public searchService: SearchService, private overlay: Overlay, private viewContainerRef: ViewContainerRef, private cdr: ChangeDetectorRef - ) { - this.searchFacade.setPageSize(15) + ) {} + + ngOnInit(): void { this.searchFacade.resetSearch() - } - editRecord(record: CatalogRecord) { - this.router - .navigate(['/edit', record.uniqueIdentifier]) - .catch((err) => console.error(err)) - } + 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 = '' - duplicateRecord(record: CatalogRecord) { - this.router - .navigate(['/duplicate', record.uniqueIdentifier]) - .catch((err) => console.error(err)) + 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) } createRecord() { diff --git a/apps/metadata-editor/src/app/records/my-library/my-library.component.html b/apps/metadata-editor/src/app/records/my-library/my-library.component.html deleted file mode 100644 index 938169810b..0000000000 --- a/apps/metadata-editor/src/app/records/my-library/my-library.component.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.html b/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.html deleted file mode 100644 index 0d7ebda3a1..0000000000 --- a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.spec.ts b/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.spec.ts deleted file mode 100644 index 981f406c9a..0000000000 --- a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { MyOrgRecordsComponent } from './my-org-records.component' -import { of } from 'rxjs' -import { MyOrgService } from '@geonetwork-ui/feature/catalog' -import { someOrganizationsFixture } from '@geonetwork-ui/common/fixtures' -import { SearchFacade } from '@geonetwork-ui/feature/search' -import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' -import { EditorRouterService } from '../../router.service' - -const orgDataMock = { - orgName: 'wizard-org', - logoUrl: 'https://my-geonetwork.org/logo11.png', - recordCount: 10, - userCount: 3, - userList: [ - { - id: '161', - profile: 'Administrator', - username: 'ghost16', - name: 'Ghost', - surname: 'Old', - email: 'old.ghost@wiz.fr', - organisation: 'wizard-org', - profileIcon: - 'https://www.gravatar.com/avatar/dbdffd183622800bcf8587328daf43a6?d=mp', - }, - { - id: '3', - profile: 'Editor', - username: 'voldy63', - name: 'Lord', - surname: 'Voldemort', - email: 'lord.voldy@wiz.com', - organisation: 'wizard-org', - }, - { - id: '4', - profile: 'Editor', - username: 'al.dumble98', - name: 'Albus', - surname: 'Dumbledore', - email: 'albus.dumble@wiz.com', - organisation: 'wizard-org', - }, - ], -} - -const myOrgServiceMock = { - myOrgData$: of(orgDataMock), -} - -const organisationsServiceMock = { - organisations$: of(someOrganizationsFixture), -} - -const searchFacadeMock = { - resetSearch: jest.fn(), -} - -const routeServiceMock = { - getDatahubSearchRoute: jest.fn(), -} - -describe('MyOrgRecordsComponent', () => { - let component: MyOrgRecordsComponent - let searchFacade: SearchFacade - let myOrgService: MyOrgService - let orgServiceInterface: OrganizationsServiceInterface - let routerService: EditorRouterService - - beforeEach(() => { - orgServiceInterface = organisationsServiceMock as any - myOrgService = myOrgServiceMock as any - searchFacade = searchFacadeMock as any - routerService = routeServiceMock as any - - component = new MyOrgRecordsComponent( - myOrgService, - searchFacade, - orgServiceInterface, - routerService - ) - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) - - describe('Get organization users info', () => { - let orgData - - beforeEach(() => { - orgData = null - component.orgData$.subscribe((data) => (orgData = data)) - }) - - it('should get the org name', () => { - expect(orgData.orgName).toEqual('wizard-org') - }) - - it('should get the org logo', () => { - expect(orgData.logoUrl).toEqual('https://my-geonetwork.org/logo11.png') - }) - - it('should get the list of users', () => { - expect(orgData.userList).toEqual([ - { - id: '161', - profile: 'Administrator', - username: 'ghost16', - name: 'Ghost', - surname: 'Old', - email: 'old.ghost@wiz.fr', - organisation: 'wizard-org', - profileIcon: - 'https://www.gravatar.com/avatar/dbdffd183622800bcf8587328daf43a6?d=mp', - }, - { - id: '3', - profile: 'Editor', - username: 'voldy63', - name: 'Lord', - surname: 'Voldemort', - email: 'lord.voldy@wiz.com', - organisation: 'wizard-org', - }, - { - id: '4', - profile: 'Editor', - username: 'al.dumble98', - name: 'Albus', - surname: 'Dumbledore', - email: 'albus.dumble@wiz.com', - organisation: 'wizard-org', - }, - ]) - }) - }) - it('should generate the correct Datahub URL', () => { - // Mock the router method and set orgData - component.router.getDatahubSearchRoute = () => 'http://example.com' - - const datahubUrl = component.getDatahubUrl() - - // Assert that the generated URL contains the orgName - expect(datahubUrl).toContain('publisher=wizard-org') - }) -}) diff --git a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts b/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts deleted file mode 100644 index eb6e675179..0000000000 --- a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component } from '@angular/core' -import { CommonModule } from '@angular/common' -import { TranslateModule } from '@ngx-translate/core' -import { RecordsListComponent } from '../records-list.component' -import { MyOrgService } from '@geonetwork-ui/feature/catalog' -import { SearchFacade } from '@geonetwork-ui/feature/search' -import { Organization } from '@geonetwork-ui/common/domain/model/record' -import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' -import { EditorRouterService } from '../../router.service' -import { take } from 'rxjs' - -@Component({ - selector: 'md-editor-my-org-records', - templateUrl: './my-org-records.component.html', - styleUrls: ['./my-org-records.component.css'], - standalone: true, - imports: [CommonModule, TranslateModule, RecordsListComponent], -}) -export class MyOrgRecordsComponent { - orgData$ = this.myOrgRecordsService.myOrgData$ - userCount: number - orgName: string - logoUrl: string - - constructor( - private myOrgRecordsService: MyOrgService, - public searchFacade: SearchFacade, - public orgService: OrganizationsServiceInterface, - public router: EditorRouterService - ) { - this.searchFacade.resetSearch() - this.orgData$.pipe(take(1)).subscribe((data) => { - this.userCount = data.userCount - this.orgName = data.orgName - this.logoUrl = data.logoUrl - this.searchByOrganisation({ name: data.orgName }) - }) - } - - searchByOrganisation(organisation: Organization) { - this.orgService - .getFiltersForOrgs([organisation]) - .subscribe((filters) => this.searchFacade.setFilters(filters)) - } - - getDatahubUrl(): string { - const url = new URL( - this.router.getDatahubSearchRoute(), - window.location.toString() - ) - url.searchParams.append('publisher', this.orgName) - return url.toString() - } -} 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 049f7af0c3..577f01a0ee 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,5 +1,73 @@ - - +
+
+ +

+ dashboard.records.search +

+
+ +
+
+ +

+ dashboard.records.myRecords +

+
+ +
+
+
+
+
+ dashboard.myRecords.publishedMetadatas +
+
+ dashboard.myRecords.currentlyEdited +
+
+ + dashboard.importRecord + keyboard_arrow_down + keyboard_arrow_up + + + + + + edit_document + dashboard.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 7a2c62c186..af64967042 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 @@ -1,84 +1,94 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { MyRecordsComponent } from './my-records.component' -import { FieldsService, SearchFacade } from '@geonetwork-ui/feature/search' -import { Component, importProvidersFrom, Input } from '@angular/core' -import { TranslateModule } from '@ngx-translate/core' -import { RecordsListComponent } from '../records-list.component' +import { + FieldsService, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' +import { ChangeDetectionStrategy } from '@angular/core' import { BehaviorSubject, of } from 'rxjs' import { barbieUserFixture } from '@geonetwork-ui/common/fixtures' import { EditorRouterService } from '../../router.service' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' - -@Component({ - selector: 'md-editor-records-list', - template: '', - standalone: true, -}) -export class MockRecordsListComponent { - @Input() linkToDatahub: string -} -const user = barbieUserFixture() - -class SearchFacadeMock { - resetSearch = jest.fn() - updateFilters = jest.fn() -} -class EditorRouterServiceMock { - getDatahubSearchRoute = jest.fn(() => `/datahub/`) -} - -class AuthServiceMock { - user$ = new BehaviorSubject(user) - authReady = jest.fn(() => this._authSubject$) - _authSubject$ = new BehaviorSubject({}) -} - -class FieldsServiceMock { - buildFiltersFromFieldValues = jest.fn((val) => of(val)) -} - -const me$ = new BehaviorSubject(barbieUserFixture()) -class PlatformServiceMock { - getMe = jest.fn(() => me$) -} +import { MockBuilder, MockInstance, MockProviders } from 'ng-mocks' +import { ActivatedRoute, Router } from '@angular/router' +import { TranslateModule } from '@ngx-translate/core' describe('MyRecordsComponent', () => { + MockInstance.scope() + let component: MyRecordsComponent let fixture: ComponentFixture + + let router: Router let searchFacade: SearchFacade + let platformService: PlatformServiceInterface + let fieldsService: FieldsService + + const user = barbieUserFixture() + + beforeEach(() => { + return MockBuilder(MyRecordsComponent) + }) beforeEach(() => { TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], providers: [ - importProvidersFrom(TranslateModule.forRoot()), - { - provide: FieldsService, - useClass: FieldsServiceMock, - }, - { - provide: SearchFacade, - useClass: SearchFacadeMock, - }, - { - provide: PlatformServiceInterface, - useClass: PlatformServiceMock, - }, - { - provide: EditorRouterService, - useClass: EditorRouterServiceMock, - }, + MockProviders( + FieldsService, + SearchFacade, + PlatformServiceInterface, + EditorRouterService, + ActivatedRoute, + SearchService + ), ], }).overrideComponent(MyRecordsComponent, { - remove: { - imports: [RecordsListComponent], - }, - add: { - imports: [MockRecordsListComponent], + set: { + changeDetection: ChangeDetectionStrategy.Default, }, }) - searchFacade = TestBed.inject(SearchFacade) + + MockInstance(ActivatedRoute, 'snapshot', jest.fn(), 'get').mockReturnValue({ + paramMap: new Map([['paramId', 'paramValue']]), + queryParams: new Map([['paramId', 'paramValue']]), + }) + fixture = TestBed.createComponent(MyRecordsComponent) + + searchFacade = TestBed.inject(SearchFacade) + router = TestBed.inject(Router) + platformService = TestBed.inject(PlatformServiceInterface) + fieldsService = TestBed.inject(FieldsService) + + router.navigate = jest.fn().mockReturnValue(Promise.resolve(true)) + + platformService.getMe = jest.fn( + () => new BehaviorSubject(barbieUserFixture()) + ) + + fieldsService.buildFiltersFromFieldValues = jest.fn((fieldValues) => + of( + Object.keys(fieldValues).reduce( + (_, curr) => ({ + [curr]: fieldValues[curr], + }), + {} + ) + ) + ) + + searchFacade.searchFilters$ = new BehaviorSubject({ any: 'scot' }) + searchFacade.resetSearch = jest.fn(() => this) + searchFacade.updateFilters = jest.fn(() => this) + searchFacade.setFilters = jest.fn(() => this) + searchFacade.setSortBy = jest.fn(() => this) + searchFacade.setPageSize = jest.fn(() => this) + searchFacade.setConfigRequestFields = jest.fn(() => this) + component = fixture.componentInstance + fixture.detectChanges() }) @@ -96,12 +106,4 @@ describe('MyRecordsComponent', () => { }) }) }) - - describe('datahub url', () => { - it('get correct url', () => { - expect(component.getDatahubUrl()).toEqual( - 'http://localhost/datahub/?owner=46798' - ) - }) - }) }) 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 24246ba0f7..2ae25aea7e 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 @@ -1,33 +1,109 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +import { + ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + OnInit, + TemplateRef, + ViewChild, + ViewContainerRef, +} from '@angular/core' import { CommonModule } from '@angular/common' import { TranslateModule } from '@ngx-translate/core' import { RecordsListComponent } from '../records-list.component' -import { FieldsService, SearchFacade } from '@geonetwork-ui/feature/search' -import { AuthService } from '@geonetwork-ui/api/repository' -import { EditorRouterService } from '../../router.service' -import { Subscription } from 'rxjs' +import { + FeatureSearchModule, + FieldsService, + ResultsTableContainerComponent, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' +import { Observable, Subscription } from 'rxjs' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { UiElementsModule } from '@geonetwork-ui/ui/elements' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' +import { ActivatedRoute, Router } from '@angular/router' +import { Overlay, OverlayRef } from '@angular/cdk/overlay' +import { TemplatePortal } from '@angular/cdk/portal' +import { allSearchFields } from '../all-records/all-records.component' +import { RecordsCountComponent } from '../records-count/records-count.component' +import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { MatIconModule } from '@angular/material/icon' +import { ImportRecordComponent } from '@geonetwork-ui/feature/editor' +import { map } from 'rxjs/operators' @Component({ selector: 'md-editor-my-records', templateUrl: './my-records.component.html', styleUrls: ['./my-records.component.css'], standalone: true, - imports: [CommonModule, TranslateModule, RecordsListComponent], + imports: [ + CommonModule, + TranslateModule, + RecordsListComponent, + ResultsTableContainerComponent, + UiElementsModule, + RecordsCountComponent, + ButtonComponent, + MatIconModule, + ImportRecordComponent, + FeatureSearchModule, + ], }) export class MyRecordsComponent implements OnInit, OnDestroy { private sub: Subscription - private ownerId: string + ownerId: string + + @ViewChild('importRecordButton', { read: ElementRef }) + private importRecordButton!: ElementRef + @ViewChild('template') template!: TemplateRef + private overlayRef!: OverlayRef + + searchText$: Observable = + this.searchFacade.searchFilters$.pipe( + map((filters) => ('any' in filters ? (filters['any'] as string) : null)) + ) + + isImportMenuOpen = false constructor( - public fieldsService: FieldsService, - public searchFacade: SearchFacade, + private router: Router, + private activedRoute: ActivatedRoute, + private searchFacade: SearchFacade, + public searchService: SearchService, private platformService: PlatformServiceInterface, - private router: EditorRouterService + private fieldsService: FieldsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cdr: ChangeDetectorRef ) {} 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.sub = this.platformService.getMe().subscribe((user) => { this.ownerId = user.id this.fieldsService @@ -38,15 +114,53 @@ export class MyRecordsComponent implements OnInit, OnDestroy { }) } - getDatahubUrl(): string { - const url = new URL( - `${this.router.getDatahubSearchRoute()}`, - this.router.getDatahubSearchRoute().startsWith('http') - ? this.router.getDatahubSearchRoute() - : window.location.toString() - ) - url.searchParams.append('owner', this.ownerId) - return url.toString() + duplicateRecord(record: CatalogRecord) { + this.router + .navigate(['/duplicate', record.uniqueIdentifier]) + .catch((err) => console.error(err)) + } + + createRecord() { + this.router.navigate(['/create']).catch((err) => console.error(err)) + } + + duplicateExternalRecord() { + this.isImportMenuOpen = true + + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo(this.importRecordButton) + .withPositions([ + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + }, + ]) + + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'cdk-overlay-transparent-backdrop', + positionStrategy: positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.reposition(), + }) + + const portal = new TemplatePortal(this.template, this.viewContainerRef) + + this.overlayRef.attach(portal) + + this.overlayRef.backdropClick().subscribe(() => { + this.closeImportMenu() + }) + } + + closeImportMenu() { + if (this.overlayRef) { + this.isImportMenuOpen = false + this.overlayRef.dispose() + this.cdr.markForCheck() + } } ngOnDestroy(): void { diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index 710f8cfa65..3a9a806e9b 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -1,68 +1,17 @@ -
-
-
-
- - -
-
-

- {{ title }} -

-
- -
-
-
-
- {{ searchFacade.resultsHits$ | async }} - dashboard.records.publishedRecords - - {{ userCount }}  - - dashboard.records.users - - -
+
+ + +
+
-
- -
-
- -
-
-
-
+
diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts index c930da2a02..96645eefda 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common' -import { Component, Input } from '@angular/core' +import { Component } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { Router } from '@angular/router' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' @@ -14,18 +14,6 @@ import { TranslateModule } from '@ngx-translate/core' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { RecordsCountComponent } from './records-count/records-count.component' -const includes = [ - 'uuid', - 'resourceTitleObject', - 'createDate', - 'changeDate', - 'userinfo', - 'cl_status', - 'isPublishedToAll', - 'link', - 'owner', -] - @Component({ selector: 'md-editor-records-list', templateUrl: './records-list.component.html', @@ -43,27 +31,16 @@ const includes = [ ], }) export class RecordsListComponent { - @Input() title: string - @Input() logo: string - @Input() linkToDatahub?: string - @Input() userCount = 0 - constructor( private router: Router, public searchFacade: SearchFacade, - public searchService: SearchService - ) { - this.searchFacade.setPageSize(15).setConfigRequestFields(includes) - } + private searchService: SearchService + ) {} paginate(page: number) { this.searchService.setPage(page) } - createRecord() { - this.router.navigate(['/create']) - } - editRecord(record: CatalogRecord) { this.router.navigate(['/edit', record.uniqueIdentifier]) } @@ -71,8 +48,4 @@ export class RecordsListComponent { duplicateRecord(record: CatalogRecord) { this.router.navigate(['/duplicate', record.uniqueIdentifier]) } - - showUsers() { - this.router.navigate(['/users/my-org']) - } } diff --git a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.css b/apps/metadata-editor/src/app/records/templates/templates.component.css similarity index 100% rename from apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.css rename to apps/metadata-editor/src/app/records/templates/templates.component.css diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.css b/apps/metadata-editor/src/app/records/templates/templates.component.html similarity index 100% rename from apps/metadata-editor/src/app/records/search-records/search-records-list.component.css rename to apps/metadata-editor/src/app/records/templates/templates.component.html diff --git a/apps/metadata-editor/src/app/records/my-library/my-library.component.spec.ts b/apps/metadata-editor/src/app/records/templates/templates.component.spec.ts similarity index 83% rename from apps/metadata-editor/src/app/records/my-library/my-library.component.spec.ts rename to apps/metadata-editor/src/app/records/templates/templates.component.spec.ts index f44522a61a..bf2c40dc05 100644 --- a/apps/metadata-editor/src/app/records/my-library/my-library.component.spec.ts +++ b/apps/metadata-editor/src/app/records/templates/templates.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { MyLibraryComponent } from './my-library.component' +import { TemplatesComponent } from './templates.component' import { SearchFacade } from '@geonetwork-ui/feature/search' import { Component, importProvidersFrom } from '@angular/core' import { TranslateModule } from '@ngx-translate/core' @@ -17,8 +17,8 @@ class SearchFacadeMock { } describe('MyLibraryComponent', () => { - let component: MyLibraryComponent - let fixture: ComponentFixture + let component: TemplatesComponent + let fixture: ComponentFixture let searchFacade: SearchFacade beforeEach(() => { @@ -30,7 +30,7 @@ describe('MyLibraryComponent', () => { useClass: SearchFacadeMock, }, ], - }).overrideComponent(MyLibraryComponent, { + }).overrideComponent(TemplatesComponent, { remove: { imports: [RecordsListComponent], }, @@ -39,7 +39,7 @@ describe('MyLibraryComponent', () => { }, }) searchFacade = TestBed.inject(SearchFacade) - fixture = TestBed.createComponent(MyLibraryComponent) + fixture = TestBed.createComponent(TemplatesComponent) component = fixture.componentInstance fixture.detectChanges() }) diff --git a/apps/metadata-editor/src/app/records/my-library/my-library.component.ts b/apps/metadata-editor/src/app/records/templates/templates.component.ts similarity index 74% rename from apps/metadata-editor/src/app/records/my-library/my-library.component.ts rename to apps/metadata-editor/src/app/records/templates/templates.component.ts index b3ad45edb6..56e02ba67b 100644 --- a/apps/metadata-editor/src/app/records/my-library/my-library.component.ts +++ b/apps/metadata-editor/src/app/records/templates/templates.component.ts @@ -5,13 +5,13 @@ import { RecordsListComponent } from '../records-list.component' import { SearchFacade } from '@geonetwork-ui/feature/search' @Component({ - selector: 'md-editor-my-library', - templateUrl: './my-library.component.html', - styleUrls: ['./my-library.component.css'], + selector: 'md-editor-templates', + templateUrl: './templates.component.html', + styleUrls: ['./templates.component.css'], standalone: true, imports: [CommonModule, TranslateModule, RecordsListComponent], }) -export class MyLibraryComponent { +export class TemplatesComponent { constructor(public searchFacade: SearchFacade) { this.searchFacade.resetSearch() } diff --git a/libs/feature/editor/src/lib/components/import-record/import-record.component.spec.ts b/libs/feature/editor/src/lib/components/import-record/import-record.component.spec.ts index 1a85b10858..e1b1624e94 100644 --- a/libs/feature/editor/src/lib/components/import-record/import-record.component.spec.ts +++ b/libs/feature/editor/src/lib/components/import-record/import-record.component.spec.ts @@ -2,11 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { ImportRecordComponent } from './import-record.component' import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' import { Router } from '@angular/router' -import { TranslateModule, TranslateService } from '@ngx-translate/core' +import { TranslateService } from '@ngx-translate/core' import { NotificationsService } from '@geonetwork-ui/feature/notifications' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { of, throwError } from 'rxjs' -import { MockBuilder, MockComponent, MockModule, MockProviders } from 'ng-mocks' +import { MockBuilder, MockProviders } from 'ng-mocks' describe('ImportRecordComponent', () => { let component: ImportRecordComponent @@ -22,10 +22,6 @@ describe('ImportRecordComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ - MockComponent(ImportRecordComponent), - MockModule(TranslateModule.forRoot()), - ], providers: [ MockProviders( Router, 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 9fc85568c8..40f4569d7f 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 @@ -4,6 +4,8 @@ [selectedRecordsIdentifiers]="selectedRecords$ | async" [sortOrder]="sortBy$ | async" [isUnsavedDraft]="isUnsavedDraft" + [canDelete]="canDelete" + [canDuplicate]="canDuplicate" (recordClick)="handleRecordClick($event)" (duplicateRecord)="handleDuplicateRecord($event)" (deleteRecord)="handleDeleteRecord($event)" diff --git a/libs/feature/search/src/lib/results-table/results-table-container.component.ts b/libs/feature/search/src/lib/results-table/results-table-container.component.ts index c477558cb7..1b3d5f2c98 100644 --- a/libs/feature/search/src/lib/results-table/results-table-container.component.ts +++ b/libs/feature/search/src/lib/results-table/results-table-container.component.ts @@ -24,6 +24,9 @@ import { TranslateService } from '@ngx-translate/core' imports: [CommonModule, ResultsTableComponent], }) export class ResultsTableContainerComponent implements OnDestroy { + @Input() canDuplicate: (record: CatalogRecord) => boolean = () => true + @Input() canDelete: (record: CatalogRecord) => boolean = () => true + @Output() recordClick = new EventEmitter() @Output() duplicateRecord = new EventEmitter() diff --git a/libs/ui/inputs/src/lib/badge/badge.component.html b/libs/ui/inputs/src/lib/badge/badge.component.html index cfa8d07b99..f567849682 100644 --- a/libs/ui/inputs/src/lib/badge/badge.component.html +++ b/libs/ui/inputs/src/lib/badge/badge.component.html @@ -11,8 +11,7 @@ type="light" *ngIf="removable" (buttonClick)="removeBadge()" - class="ml-1 -my-[0.4em] -mr-[0.45em]" - extraClass="border-0" + extraClass="text-xs border-0 px-0 py-0" style=" --gn-ui-button-padding: 0; --gn-ui-button-font-size: 0.8em; diff --git a/libs/ui/layout/src/lib/interactive-table/interactive-table.component.css b/libs/ui/layout/src/lib/interactive-table/interactive-table.component.css index df5d1f2711..c808088fc4 100644 --- a/libs/ui/layout/src/lib/interactive-table/interactive-table.component.css +++ b/libs/ui/layout/src/lib/interactive-table/interactive-table.component.css @@ -7,7 +7,7 @@ } .table-header-cell { - @apply text-gray-700 px-4 py-5 flex items-center truncate bg-white; + @apply text-gray-700 px-3 py-3 flex items-center truncate bg-white; } button.table-header-cell { diff --git a/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html b/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html index 143497cfbb..eb8d5b500f 100644 --- a/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +++ b/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html @@ -1,5 +1,5 @@
-
+