diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index 9394e2df93..ad270663d4 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -187,7 +187,7 @@ describe('dashboard', () => { cy.get('@draft') .children('div') .eq(5) - .should('contain', ' Not published ') + .should('include.text', 'Not published') cy.get('@draft').children('div').eq(6).should('contain', ' - ') cy.clearRecordDrafts() }) @@ -204,23 +204,26 @@ describe('dashboard', () => { .find('span') .invoke('text') .should('eq', 'admin admin') - cy.get('@record').children('div').eq(5).should('contain', ' Published ') + cy.get('@record') + .children('div') + .eq(5) + .find('span') + .should('include.text', 'Published') cy.get('@record').children('div').eq(6).should('not.contain', ' - ') cy.clearRecordDrafts() }) }) + describe('navigation', () => { beforeEach(() => { cy.login('admin', 'admin', false) cy.visit('/catalog/search') }) - describe('search input', () => { - it('should filter the dashboard based on the search input', () => { - cy.get('gn-ui-autocomplete').type('Mat') - cy.get('mat-option').first().click() - cy.get('gn-ui-interactive-table') + describe('all records', () => { + it('should display the correct amount of records', () => { + cy.get('gn-ui-results-table') .find('[data-cy="table-row"]') - .should('have.length', '1') + .should('have.length', '15') }) }) describe('my records', () => { @@ -232,6 +235,83 @@ 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) + }) + }) + }) + }) + + describe('search', () => { + function checkDashboardFiltered() { + cy.get('gn-ui-autocomplete').type('velo{enter}') + cy.get('gn-ui-interactive-table') + .find('[data-cy="table-row"]') + .should('have.length', '1') + } + function checkAutocompleteSelected() { + cy.get('gn-ui-autocomplete').type('velo') + cy.get('mat-option').first().click() + cy.url().should('include', '/edit/accroche_velos') + } + describe('allRecords search input', () => { + beforeEach(() => { + cy.login('admin', 'admin', false) + cy.visit('/catalog/search') + }) + it('should filter the dashboard based on the search input', () => { + checkDashboardFiltered() + }) + it('should navigate to the record selected in the autocomplete', () => { + checkAutocompleteSelected() + }) + it('should clear the search input when navigating to my records', () => { + cy.get('gn-ui-autocomplete').type('velo') + cy.get('md-editor-dashboard-menu').find('a').eq(5).click() + cy.get('gn-ui-autocomplete').should('have.value', '') + }) + it('should hide the search input when navigating to my drafts', () => { + cy.get('md-editor-dashboard-menu').find('a').eq(6).click() + cy.get('gn-ui-autocomplete').should('not.exist') + }) + }) + describe('myRecords search input', () => { + beforeEach(() => { + cy.login('admin', 'admin', false) + cy.visit('/my-space/my-records') + }) + it('should filter the dashboard based on the search input', () => { + checkDashboardFiltered() + }) + it('should navigate to the record selected in the autocomplete', () => { + checkAutocompleteSelected() + }) + it('should clear the search input when navigating to all records', () => { + cy.get('gn-ui-autocomplete').type('velo') + cy.get('md-editor-dashboard-menu').find('a').first().click() + cy.get('gn-ui-autocomplete').should('have.value', '') + }) }) }) }) diff --git a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts index c6492c2616..70ec2fef0a 100644 --- a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts @@ -1,9 +1,10 @@ /* eslint-disable cypress/no-unnecessary-waiting */ describe('editor form', () => { - let recordUuid + let recordUuid: any before(() => { cy.login('admin', 'admin', false) + cy.viewport(1920, 2400) cy.clearRecordDrafts() @@ -53,6 +54,7 @@ describe('editor form', () => { }) beforeEach(() => { cy.login('admin', 'admin', false) + cy.visit('/catalog/search') cy.wrap(recordUuid).as('recordUuid') cy.get('@recordUuid').then((recordUuid) => { diff --git a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts deleted file mode 100644 index 14ddf94cf9..0000000000 --- a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts +++ /dev/null @@ -1,41 +0,0 @@ -describe('my-org', () => { - beforeEach(() => { - cy.login('barbie', 'p4ssworD_', false) - cy.intercept({ - method: 'GET', - url: '/geonetwork/srv/api/userselections/0/101', - }).as('dataGetFirst') - cy.visit(`/catalog/my-org`) - cy.wait('@dataGetFirst').its('response.statusCode').should('equal', 200) - }) - describe('my-org display', () => { - it('should show my-org name and logo', () => { - cy.get('h1').should('not.have.text', '') - cy.get('gn-ui-thumbnail') - }) - it('should show the user and record count', () => { - cy.get('[data-cy=link-to-datahub]').should('not.have.text', '') - cy.get('[data-cy=link-to-users]').should('not.have.text', '') - }) - it('should show my-org records', () => { - cy.get('gn-ui-interactive-table .contents').should('have.length.above', 1) - }) - }) - describe('routing', () => { - it('should access the datahub with a filter', () => { - cy.get('[data-cy=link-to-datahub]') - .should('have.attr', 'href') - .then((href) => { - expect(href).to.include('search?publisher=Barbie+Inc') - }) - }) - it('should access the user list page and show my-org users', () => { - cy.visit(`/catalog/my-org`) - cy.get('[data-cy=link-to-users]').click() - cy.url().should('include', '/users/my-org') - cy.get('gn-ui-interactive-table .contents').should('have.length.above', 1) - cy.get('h1').should('not.have.text', '') - cy.get('gn-ui-thumbnail') - }) - }) -}) diff --git a/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts b/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts index ba6d551c22..20afc25802 100644 --- a/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts @@ -4,6 +4,7 @@ describe('record-actions', () => { cy.visit('/catalog/search') }) describe('delete', () => { + const recordId = `TEST_RECORD_${Date.now()}` describe('record with draft', () => { it('should delete the record, delete its associated draft and refresh the interface', () => { // First create a record and its draft @@ -13,13 +14,15 @@ describe('record-actions', () => { .as('abstractField') .focus() cy.get('@abstractField').type('record abstract') + cy.get('[data-test="recordTitleInput"]').click() + cy.get('[data-test="recordTitleInput"]').type('{selectAll}{backspace}') + cy.get('[data-test="recordTitleInput"]').type(recordId) cy.intercept({ method: 'PUT', pathname: '**/records', }).as('insertRecord') cy.get('md-editor-publish-button').click() cy.wait('@insertRecord') - cy.get('@abstractField').focus() cy.get('@abstractField').type('draft abstract') // Assert that the draft exists in the local storage cy.editor_readFormUniqueIdentifier().then((uniqueIdentifier) => @@ -31,7 +34,7 @@ describe('record-actions', () => { ) cy.visit('/my-space/my-records') cy.get('[data-cy="table-row"]') - .contains('My new record') + .contains(recordId) .should('have.length', 1) cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '1') // Delete the record @@ -39,10 +42,9 @@ describe('record-actions', () => { cy.get('[data-test="record-menu-delete-button"]').click() cy.get('[data-cy="confirm-button"]').click() cy.get('[data-cy="table-row"]') - .contains('My new record') + .contains(recordId) .should('have.length', 0) cy.get('gn-ui-notification').should('contain', 'Delete success') - cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '0') }) }) @@ -51,6 +53,9 @@ describe('record-actions', () => { // First create a draft cy.get('[data-cy="create-record"]').click() cy.url().should('include', '/create') + cy.get('[data-test="recordTitleInput"]').click() + cy.get('[data-test="recordTitleInput"]').type('{selectAll}{backspace}') + cy.get('[data-test="recordTitleInput"]').type(recordId) cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea') .as('abstractField') .focus() @@ -63,17 +68,14 @@ describe('record-actions', () => { }) cy.visit('/my-space/my-draft') cy.get('[data-cy="table-row"]') - .contains('My new record') + .contains(recordId) .should('have.length', 1) cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '1') // Delete the draft cy.get('[data-test="record-menu-button"]').click() cy.get('[data-test="record-menu-delete-button"]').click() cy.get('[data-cy="confirm-button"]').click() - cy.get('[data-cy="table-row"]') - .contains('New record') - .should('have.length', 0) - cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '0') + cy.get('[data-cy="table-row"]').should('not.exist') }) }) }) diff --git a/apps/metadata-editor/src/app/app.routes.ts b/apps/metadata-editor/src/app/app.routes.ts index d8c5da1256..f2eac576b8 100644 --- a/apps/metadata-editor/src/app/app.routes.ts +++ b/apps/metadata-editor/src/app/app.routes.ts @@ -3,14 +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 { 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' +import { MyRecordsStateWrapperComponent } from './records/my-records/my-records-state-wrapper.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, - }, ], }, { @@ -66,7 +60,7 @@ export const appRoutes: Route[] = [ { path: 'my-records', title: 'My Records', - component: MyRecordsComponent, + component: MyRecordsStateWrapperComponent, pathMatch: 'prefix', }, { @@ -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 7a2a11d2ca..425d9d2591 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 @@ -71,11 +71,15 @@ > 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..7a34507ebf 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,14 @@ :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; +:host ::ng-deep gn-ui-autocomplete gn-ui-button button { + @apply border-none shadow-none hover:shadow-none text-gray-500 hover:text-gray-600 rounded-3xl; +} +:host ::ng-deep gn-ui-autocomplete gn-ui-button:first-of-type button { + @apply right-0 left-auto; +} +:host ::ng-deep gn-ui-autocomplete gn-ui-button:last-of-type button { + @apply left-0 right-auto; } ::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 64cc3f9525..e4627b2381 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,35 +1,6 @@ -
-
- -
-
- - - - - - -
+
+
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..4450682870 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 @@ -2,24 +2,15 @@ import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { SearchHeaderComponent } from './search-header.component' -import { BehaviorSubject, of } from 'rxjs' -import { barbieUserFixture } from '@geonetwork-ui/common/fixtures' +import { of } from 'rxjs' 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' - -class AvatarServiceInterfaceMock { - getPlaceholder = () => of('http://placeholder.com') - getProfileIcon = (hash: string) => of(`${hash}`) -} - -const me$ = new BehaviorSubject(barbieUserFixture()) -class PlatformServiceMock { - getMe = jest.fn(() => me$) -} +import { SearchService } from '@geonetwork-ui/feature/search' +import { MockProvider, MockProviders } from 'ng-mocks' +import { RouterFacade } from '@geonetwork-ui/feature/router' describe('SearchHeaderComponent', () => { let component: SearchHeaderComponent @@ -34,14 +25,14 @@ describe('SearchHeaderComponent', () => { TranslateModule.forRoot(), ], providers: [ - { - provide: AvatarServiceInterface, - useClass: AvatarServiceInterfaceMock, - }, - { - provide: PlatformServiceInterface, - useClass: PlatformServiceMock, - }, + MockProviders( + AvatarServiceInterface, + PlatformServiceInterface, + SearchService + ), + MockProvider(RouterFacade, { + currentRoute$: of(null), + }), ], }) .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..1025a8590c 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 @@ -1,12 +1,13 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component } from '@angular/core' +import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { LetDirective } from '@ngrx/component' import { FeatureSearchModule } 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' +import { Router } from '@angular/router' @Component({ selector: 'md-editor-search-header', @@ -28,7 +29,11 @@ export class SearchHeaderComponent { activeBtn = false constructor( - public platformService: PlatformServiceInterface, - private avatarService: AvatarServiceInterface + private avatarService: AvatarServiceInterface, + private router: Router ) {} + + handleItemSelection(item: CatalogRecord) { + this.router.navigate(['edit', item.uniqueIdentifier]) + } } diff --git a/apps/metadata-editor/src/app/records/all-records/all-records.component.css b/apps/metadata-editor/src/app/records/all-records/all-records.component.css new file mode 100644 index 0000000000..157df3d655 --- /dev/null +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.css @@ -0,0 +1,4 @@ +/* set max-height for overflow-y-auto */ +main { + max-height: calc(100vh - 60px); +} diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html b/apps/metadata-editor/src/app/records/all-records/all-records.component.html similarity index 68% rename from apps/metadata-editor/src/app/records/search-records/search-records-list.component.html rename to apps/metadata-editor/src/app/records/all-records/all-records.component.html index 075ce8be33..b63f271f04 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.html @@ -1,4 +1,7 @@ -
+
+ +
+

class="flex flex-row items-center mx-[32px] my-[16px] py-[8px] gap-[16px]" >
- dashboard.results.listMetadata + dashboard.results.listMetadata
- dashboard.results.listResources + dashboard.results.listResources

-
- - -
-
- -
-
-
+
diff --git a/apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts b/apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts new file mode 100644 index 0000000000..a1bc98c57d --- /dev/null +++ b/apps/metadata-editor/src/app/records/all-records/all-records.component.spec.ts @@ -0,0 +1,162 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { + FieldsService, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' +import { ChangeDetectionStrategy } from '@angular/core' +import { TranslateModule } from '@ngx-translate/core' +import { BehaviorSubject, of } from 'rxjs' +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, + NG_MOCKS_ROOT_PROVIDERS, +} from 'ng-mocks' +import { EditorRouterService } from '../../router.service' +import { Overlay } from '@angular/cdk/overlay' + +describe('AllRecordsComponent', () => { + MockInstance.scope() + + const searchFilters = new BehaviorSubject({ + any: 'hello world', + }) + + let component: AllRecordsComponent + let fixture: ComponentFixture + + let router: Router + let searchFacade: SearchFacade + let platformService: PlatformServiceInterface + let fieldsService: FieldsService + + beforeEach(() => { + return MockBuilder(AllRecordsComponent) + .keep(Overlay) + .keep(NG_MOCKS_ROOT_PROVIDERS) + }) + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + providers: [ + MockProviders( + FieldsService, + SearchFacade, + PlatformServiceInterface, + EditorRouterService, + ActivatedRoute, + SearchService + ), + ], + }).overrideComponent(AllRecordsComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + }, + }) + + 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']]), + }) + + fixture = TestBed.createComponent(AllRecordsComponent) + + router = TestBed.inject(Router) + 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.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() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should map search filters to searchText$', (done) => { + component.searchText$.subscribe((text) => { + expect(text).toBe('hello world') + done() + }) + }) + + describe('when clicking createRecord', () => { + beforeEach(() => { + component.createRecord() + }) + + it('navigates to the create record page', () => { + expect(router.navigate).toHaveBeenCalledWith(['/create']) + }) + }) + + describe('when importing a record', () => { + beforeEach(() => { + component.duplicateExternalRecord() + }) + + it('sets isImportMenuOpen to true', () => { + expect(component.isImportMenuOpen).toBe(true) + }) + }) + + describe('when closing the import menu', () => { + let overlaySpy: any + + beforeEach(() => { + overlaySpy = { + dispose: jest.fn(), + } + component['overlayRef'] = overlaySpy + + component.closeImportMenu() + }) + + it('sets isImportMenuOpen to false', () => { + expect(component.isImportMenuOpen).toBe(false) + }) + + it('disposes the overlay', () => { + expect(overlaySpy.dispose).toHaveBeenCalled() + }) + }) +}) 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 80% 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..46bfbfed2e 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 @@ -13,9 +13,7 @@ import { SearchService, } 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 { RecordsCountComponent } from '../records-count/records-count.component' import { Observable } from 'rxjs' import { UiElementsModule } from '@geonetwork-ui/ui/elements' @@ -29,11 +27,14 @@ import { } from '@angular/cdk/overlay' import { TemplatePortal } from '@angular/cdk/portal' 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' @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,11 +47,13 @@ import { ImportRecordComponent } from '@geonetwork-ui/feature/editor' ImportRecordComponent, CdkOverlayOrigin, CdkConnectedOverlay, + RecordsListComponent, + SearchHeaderComponent, ], }) -export class SearchRecordsComponent { +export class AllRecordsComponent { @ViewChild('importRecordButton', { read: ElementRef }) - private importRecordButton!: ElementRef + importRecordButton!: ElementRef @ViewChild('template') template!: TemplateRef private overlayRef!: OverlayRef @@ -68,22 +71,7 @@ export class SearchRecordsComponent { private overlay: Overlay, private viewContainerRef: ViewContainerRef, private cdr: ChangeDetectorRef - ) { - this.searchFacade.setPageSize(15) - this.searchFacade.resetSearch() - } - - editRecord(record: CatalogRecord) { - this.router - .navigate(['/edit', record.uniqueIdentifier]) - .catch((err) => console.error(err)) - } - - duplicateRecord(record: CatalogRecord) { - this.router - .navigate(['/duplicate', record.uniqueIdentifier]) - .catch((err) => console.error(err)) - } + ) {} createRecord() { this.router.navigate(['/create']).catch((err) => console.error(err)) diff --git a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.css b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.css index e69de29bb2..157df3d655 100644 --- a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.css +++ b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.css @@ -0,0 +1,4 @@ +/* set max-height for overflow-y-auto */ +main { + max-height: calc(100vh - 60px); +} diff --git a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html index 24ef15fa5a..f65d41a994 100644 --- a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html +++ b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html @@ -1,4 +1,5 @@ -
+
+

dashboard.records.myDraft @@ -6,7 +7,7 @@

- 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-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.css b/apps/metadata-editor/src/app/records/my-records/my-records.component.css index e69de29bb2..157df3d655 100644 --- a/apps/metadata-editor/src/app/records/my-records/my-records.component.css +++ b/apps/metadata-editor/src/app/records/my-records/my-records.component.css @@ -0,0 +1,4 @@ +/* set max-height for overflow-y-auto */ +main { + max-height: calc(100vh - 60px); +} 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..95da1b8ed8 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,82 @@ - - +
+ +
+
+
+ +

+ 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..8bb1b4deef 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,104 @@ 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() + + const searchFilters = new BehaviorSubject({ + any: 'hello world', + }) + 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( + SearchFacade, + 'searchFilters$', + jest.fn(), + 'get' + ).mockReturnValue(searchFilters) + + 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.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() }) @@ -86,22 +106,18 @@ describe('MyRecordsComponent', () => { expect(component).toBeTruthy() }) - describe('filters', () => { - it('clears filters on init', () => { - expect(searchFacade.resetSearch).toHaveBeenCalled() - }) - it('Update filters on init', () => { + describe('filters on init', () => { + it('updates filters with owner', () => { expect(searchFacade.updateFilters).toHaveBeenCalledWith({ owner: user.id, }) }) - }) - describe('datahub url', () => { - it('get correct url', () => { - expect(component.getDatahubUrl()).toEqual( - 'http://localhost/datahub/?owner=46798' - ) + it('should map search filters to searchText$', (done) => { + component.searchText$.subscribe((text) => { + expect(text).toBe('hello world') + done() + }) }) }) }) 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..72bc39f756 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,55 +1,128 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +import { + ChangeDetectorRef, + Component, + ElementRef, + 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, +} from '@geonetwork-ui/feature/search' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { UiElementsModule } from '@geonetwork-ui/ui/elements' +import { Router } from '@angular/router' +import { Overlay, OverlayRef } from '@angular/cdk/overlay' +import { TemplatePortal } from '@angular/cdk/portal' +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 { SearchHeaderComponent } from '../../dashboard/search-header/search-header.component' +import { map, Observable } from 'rxjs' @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, + SearchHeaderComponent, + ], }) -export class MyRecordsComponent implements OnInit, OnDestroy { - private sub: Subscription - private ownerId: string +export class MyRecordsComponent implements OnInit { + @ViewChild('importRecordButton', { read: ElementRef }) + private importRecordButton!: ElementRef + @ViewChild('template') template!: TemplateRef + private overlayRef!: OverlayRef + + searchText$: Observable + + isImportMenuOpen = false constructor( - public fieldsService: FieldsService, - public searchFacade: SearchFacade, + private router: Router, + protected searchFacade: SearchFacade, private platformService: PlatformServiceInterface, - private router: EditorRouterService + private fieldsService: FieldsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cdr: ChangeDetectorRef ) {} ngOnInit() { this.searchFacade.resetSearch() - this.sub = this.platformService.getMe().subscribe((user) => { - this.ownerId = user.id + + this.platformService.getMe().subscribe((user) => { this.fieldsService .buildFiltersFromFieldValues({ owner: user.id }) .subscribe((filters) => { this.searchFacade.updateFilters(filters) }) }) - } - getDatahubUrl(): string { - const url = new URL( - `${this.router.getDatahubSearchRoute()}`, - this.router.getDatahubSearchRoute().startsWith('http') - ? this.router.getDatahubSearchRoute() - : window.location.toString() + this.searchText$ = this.searchFacade.searchFilters$.pipe( + map((filters) => ('any' in filters ? (filters['any'] as string) : null)) ) - url.searchParams.append('owner', this.ownerId) - return url.toString() } - ngOnDestroy(): void { - this.sub.unsubscribe() + 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() + } } } 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..215e2e950f 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,16 @@ -
-
+
diff --git a/apps/metadata-editor/src/app/records/records-list.component.spec.ts b/apps/metadata-editor/src/app/records/records-list.component.spec.ts index a48a0d8572..a6e006662e 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.spec.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' -import { RecordsListComponent } from './records-list.component' +import { allSearchFields, RecordsListComponent } from './records-list.component' import { Component, EventEmitter, Input, Output } from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { By } from '@angular/platform-browser' @@ -67,6 +67,7 @@ describe('RecordsListComponent', () => { let fixture: ComponentFixture let router: Router let searchService: SearchService + let searchFacade: SearchFacade beforeEach(() => { TestBed.configureTestingModule({ @@ -97,6 +98,7 @@ describe('RecordsListComponent', () => { }) router = TestBed.inject(Router) searchService = TestBed.inject(SearchService) + searchFacade = TestBed.inject(SearchFacade) fixture = TestBed.createComponent(RecordsListComponent) component = fixture.componentInstance fixture.detectChanges() @@ -106,6 +108,17 @@ describe('RecordsListComponent', () => { expect(component).toBeTruthy() }) + describe('on init', () => { + it('sets search fields', () => { + expect(searchFacade.setConfigRequestFields).toHaveBeenCalledWith( + allSearchFields + ) + }) + it('sets page size', () => { + expect(searchFacade.setPageSize).toHaveBeenCalledWith(15) + }) + }) + describe('when search results', () => { let table, pagination beforeEach(() => { 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..a286b499fe 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, OnInit } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { Router } from '@angular/router' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' @@ -14,7 +14,7 @@ import { TranslateModule } from '@ngx-translate/core' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { RecordsCountComponent } from './records-count/records-count.component' -const includes = [ +export const allSearchFields = [ 'uuid', 'resourceTitleObject', 'createDate', @@ -25,7 +25,6 @@ const includes = [ 'link', 'owner', ] - @Component({ selector: 'md-editor-records-list', templateUrl: './records-list.component.html', @@ -42,28 +41,22 @@ const includes = [ RecordsCountComponent, ], }) -export class RecordsListComponent { - @Input() title: string - @Input() logo: string - @Input() linkToDatahub?: string - @Input() userCount = 0 - +export class RecordsListComponent implements OnInit { constructor( private router: Router, public searchFacade: SearchFacade, - public searchService: SearchService - ) { - this.searchFacade.setPageSize(15).setConfigRequestFields(includes) + private searchService: SearchService + ) {} + + ngOnInit(): void { + this.searchFacade.setConfigRequestFields(allSearchFields) + this.searchFacade.setPageSize(15) } paginate(page: number) { this.searchService.setPage(page) } - createRecord() { - this.router.navigate(['/create']) - } - editRecord(record: CatalogRecord) { this.router.navigate(['/edit', record.uniqueIdentifier]) } @@ -71,8 +64,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/search-records/search-records-list.component.css b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts deleted file mode 100644 index d39c0a6771..0000000000 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' -import { SearchRecordsComponent } from './search-records-list.component' -import { - Component, - CUSTOM_ELEMENTS_SCHEMA, - EventEmitter, - importProvidersFrom, - Input, - Output, -} from '@angular/core' -import { TranslateModule } from '@ngx-translate/core' -import { BehaviorSubject } 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' - -const results = [{ md: true }] -const currentPage = 5 -const totalPages = 25 - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-results-table-container', - template: '', - standalone: true, -}) -export class ResultsTableContainerComponent { - @Output() recordClick = new EventEmitter() - @Output() duplicateRecord = new EventEmitter() -} - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-pagination-buttons', - template: '', - standalone: true, -}) -export class PaginationButtonsComponent { - @Input() currentPage = 1 - @Input() totalPages = 1 - @Input() hideButton = false - @Output() newCurrentPageEvent = new EventEmitter() -} - -@Component({ - selector: 'md-editor-records-count', - template: '', - standalone: true, -}) -export class RecordsCountComponent {} - -class SearchFacadeMock { - searchFilters$ = new BehaviorSubject({ - any: 'hello world', - }) - results$ = new BehaviorSubject(results) - currentPage$ = new BehaviorSubject(currentPage) - totalPages$ = new BehaviorSubject(totalPages) - resultsHits$ = new BehaviorSubject(1000) - setConfigRequestFields = jest.fn(() => this) - setPageSize = jest.fn(() => this) - setSortBy = jest.fn(() => this) - resetSearch = jest.fn() -} - -class SearchServiceMock { - setPage = jest.fn() - setSortBy = jest.fn() -} - -class RouterMock { - navigate = jest.fn(() => Promise.resolve(true)) -} - -describe('SearchRecordsComponent', () => { - let component: SearchRecordsComponent - let fixture: ComponentFixture - let router: Router - let searchService: SearchService - let searchFacade: SearchFacade - - beforeEach(() => { - TestBed.configureTestingModule({ - schemas: [CUSTOM_ELEMENTS_SCHEMA], - providers: [ - importProvidersFrom(TranslateModule.forRoot()), - { - provide: SearchFacade, - useClass: SearchFacadeMock, - }, - { - provide: Router, - useClass: RouterMock, - }, - { - provide: SearchService, - useClass: SearchServiceMock, - }, - ], - }).overrideComponent(SearchRecordsComponent, { - set: { - imports: [ - CommonModule, - TranslateModule, - MatIconModule, - ResultsTableContainerComponent, - PaginationButtonsComponent, - UiInputsModule, - RecordsCountComponent, - ], - }, - }) - fixture = TestBed.createComponent(SearchRecordsComponent) - router = TestBed.inject(Router) - searchService = TestBed.inject(SearchService) - searchFacade = TestBed.inject(SearchFacade) - - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) - - it('should map search filters to searchText$', (done) => { - component.searchText$.subscribe((text) => { - expect(text).toBe('hello world') - done() - }) - }) - - describe('when clicking createRecord', () => { - beforeEach(() => { - component.createRecord() - }) - - it('navigates to the create record page', () => { - expect(router.navigate).toHaveBeenCalledWith(['/create']) - }) - }) - - describe('when importing a record', () => { - beforeEach(() => { - component.duplicateExternalRecord() - }) - - it('sets isImportMenuOpen to true', () => { - expect(component.isImportMenuOpen).toBe(true) - }) - }) - - describe('when closing the import menu', () => { - let overlaySpy: any - - beforeEach(() => { - overlaySpy = { - dispose: jest.fn(), - } - component['overlayRef'] = overlaySpy - - component.closeImportMenu() - }) - - it('sets isImportMenuOpen to false', () => { - expect(component.isImportMenuOpen).toBe(false) - }) - - it('disposes the overlay', () => { - expect(overlaySpy.dispose).toHaveBeenCalled() - }) - }) - - describe('when search results', () => { - let table, pagination - beforeEach(() => { - table = fixture.debugElement.query( - By.directive(ResultsTableContainerComponent) - ).componentInstance - pagination = fixture.debugElement.query( - By.directive(PaginationButtonsComponent) - ).componentInstance - }) - it('displays record table', () => { - expect(table).toBeTruthy() - }) - it('displays pagination', () => { - expect(pagination).toBeTruthy() - expect(pagination.currentPage).toEqual(currentPage) - expect(pagination.totalPages).toEqual(totalPages) - }) - - 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/my-library/my-library.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-library/my-library.component.css rename to apps/metadata-editor/src/app/records/templates/templates.component.css 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.html 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.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/record-form/form-field/form-field.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html index d48c8df844..d5ef07d21a 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html @@ -25,6 +25,7 @@
boolean = () => true + @Input() canDelete: (record: CatalogRecord) => boolean = () => true + @Output() recordClick = new EventEmitter() @Output() duplicateRecord = new EventEmitter() subscription = new Subscription() - records$ = this.searchFacade.results$ selectedRecords$ = this.selectionService.selectedRecordsIdentifiers$ sortBy$ = this.searchFacade.sortBy$ @@ -40,7 +42,7 @@ export class ResultsTableContainerComponent implements OnDestroy { this.recordsRepository.isRecordNotYetSaved(record.uniqueIdentifier) constructor( - private searchFacade: SearchFacade, + protected searchFacade: SearchFacade, private searchService: SearchService, private selectionService: SelectionService, private recordsRepository: RecordsRepositoryInterface, 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 @@
-
+