From 1f024b15e027692310547bc4a69b76ff601879f9 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Thu, 16 May 2024 17:41:32 +0200 Subject: [PATCH 01/10] feat(datahub): Add organization page --- apps/datahub/src/app/app.module.ts | 6 +- .../organisations-page.component.html | 2 +- .../organisations-page.component.ts | 20 +- .../header-organization.component.css | 0 .../header-organization.component.html | 52 ++ .../header-organization.component.spec.ts | 70 ++ .../header-organization.component.ts | 45 ++ .../organization-details.component.css | 0 .../organization-details.component.html | 61 ++ .../organization-details.component.spec.ts | 666 ++++++++++++++++++ .../organization-details.component.ts | 28 + .../organization-page.component.css | 0 .../organization-page.component.html | 8 + .../organization-page.component.spec.ts | 74 ++ .../organization-page.component.ts | 48 ++ .../src/app/router/datahub-router.service.ts | 16 +- apps/datahub/src/styles.css | 3 + .../lib/model/record/organization.model.ts | 1 + .../catalog/src/lib/feature-catalog.module.ts | 1 - .../organisations/organisations.component.ts | 4 +- .../catalog/src/lib/organization-url.token.ts | 4 + .../router/src/lib/default/constants.ts | 1 + .../router/src/lib/default/router.config.ts | 1 + .../router/src/lib/default/router.service.ts | 14 +- .../src/lib/default/state/router.facade.ts | 10 +- .../organisation-preview.component.ts | 2 +- libs/ui/catalog/src/lib/ui-catalog.module.ts | 2 + .../src/lib/error/error.component.stories.ts | 2 +- translations/en.json | 3 + translations/es.json | 3 + translations/fr.json | 3 + translations/it.json | 3 + translations/nl.json | 3 + translations/pt.json | 3 + translations/sk.json | 3 + 35 files changed, 1143 insertions(+), 19 deletions(-) create mode 100644 apps/datahub/src/app/organization/header-organization/header-organization.component.css create mode 100644 apps/datahub/src/app/organization/header-organization/header-organization.component.html create mode 100644 apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts create mode 100644 apps/datahub/src/app/organization/header-organization/header-organization.component.ts create mode 100644 apps/datahub/src/app/organization/organization-details/organization-details.component.css create mode 100644 apps/datahub/src/app/organization/organization-details/organization-details.component.html create mode 100644 apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts create mode 100644 apps/datahub/src/app/organization/organization-details/organization-details.component.ts create mode 100644 apps/datahub/src/app/organization/organization-page/organization-page.component.css create mode 100644 apps/datahub/src/app/organization/organization-page/organization-page.component.html create mode 100644 apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts create mode 100644 apps/datahub/src/app/organization/organization-page/organization-page.component.ts create mode 100644 libs/feature/catalog/src/lib/organization-url.token.ts diff --git a/apps/datahub/src/app/app.module.ts b/apps/datahub/src/app/app.module.ts index 4cac62f591..e8cc4b54f5 100644 --- a/apps/datahub/src/app/app.module.ts +++ b/apps/datahub/src/app/app.module.ts @@ -15,7 +15,7 @@ import { import { DefaultRouterModule, ROUTE_PARAMS, - ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, RouterService, } from '@geonetwork-ui/feature/router' @@ -92,6 +92,8 @@ import { MatTabsModule } from '@angular/material/tabs' import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { RecordUserFeedbacksComponent } from './record/record-user-feedbacks/record-user-feedbacks.component' import { LetDirective } from '@ngrx/component' +import { OrganizationPageComponent } from './organization/organization-page/organization-page.component' +import { ORGANIZATION_PAGE_URL_TOKEN } from '../../../../libs/feature/catalog/src/lib/organization-url.token' export const metaReducers: MetaReducer[] = !environment.production ? [] : [] @@ -145,6 +147,7 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] searchStateId: 'mainSearch', searchRouteComponent: SearchPageComponent, recordRouteComponent: RecordPageComponent, + organizationRouteComponent: OrganizationPageComponent }), FeatureRecordModule, FeatureCatalogModule, @@ -217,6 +220,7 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] }, }, { provide: RECORD_URL_TOKEN, useValue: `${ROUTER_ROUTE_DATASET}/\${uuid}` }, + { provide: ORGANIZATION_PAGE_URL_TOKEN, useValue: `${ROUTER_ROUTE_ORGANIZATION}/\${name}` }, { provide: ORGANIZATION_URL_TOKEN, useValue: `${ROUTER_ROUTE_SEARCH}?${ROUTE_PARAMS.PUBLISHER}=\${name}`, diff --git a/apps/datahub/src/app/home/organisations-page/organisations-page.component.html b/apps/datahub/src/app/home/organisations-page/organisations-page.component.html index 9ce96ecafb..454d3b8d4d 100644 --- a/apps/datahub/src/app/home/organisations-page/organisations-page.component.html +++ b/apps/datahub/src/app/home/organisations-page/organisations-page.component.html @@ -1,5 +1,5 @@
diff --git a/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts b/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts index 3abb743666..37826aee72 100644 --- a/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts +++ b/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' -import { SearchService } from '@geonetwork-ui/feature/search' -import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { Organization } from '@geonetwork-ui/common/domain/model/record' +import { RouterFacade } from '@geonetwork-ui/feature/router' @Component({ selector: 'datahub-organisations-page', @@ -11,13 +10,18 @@ import { Organization } from '@geonetwork-ui/common/domain/model/record' }) export class OrganisationsPageComponent { constructor( - private searchService: SearchService, - private orgsService: OrganizationsServiceInterface + // private searchService: SearchService, + // private orgsService: OrganizationsServiceInterface, + private searchRouter: RouterFacade ) {} - searchByOrganisation(organisation: Organization) { - this.orgsService - .getFiltersForOrgs([organisation]) - .subscribe((filters) => this.searchService.setFilters(filters)) + // searchByOrganisation(organisation: Organization) { + // this.orgsService + // .getFiltersForOrgs([organisation]) + // .subscribe((filters) => this.searchService.setFilters(filters)) + // } + + onOrganizationSelection(organisation: Organization) { + this.searchRouter.goToOrganization(organisation.name) } } diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.css b/apps/datahub/src/app/organization/header-organization/header-organization.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.html b/apps/datahub/src/app/organization/header-organization/header-organization.component.html new file mode 100644 index 0000000000..8eb9d0b43e --- /dev/null +++ b/apps/datahub/src/app/organization/header-organization/header-organization.component.html @@ -0,0 +1,52 @@ +
+
+
+
+ + +
+
+ +
+
+
+ {{ organization.name }} +
+
+ folder +

+ {{ organization.recordCount }} +

+

+ organization.header.recordCount +

+ +

+ • +

+ {{ organization.website.href }} open_in_new +
+
+
+
diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts b/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts new file mode 100644 index 0000000000..3ea9850ee6 --- /dev/null +++ b/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts @@ -0,0 +1,70 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { MdViewFacade } from '@geonetwork-ui/feature/record' +import { SearchService } from '@geonetwork-ui/feature/search' +import { TranslateModule } from '@ngx-translate/core' +import { BehaviorSubject } from 'rxjs' + +import { HeaderOrganizationComponent } from './header-organization.component' + +jest.mock('@geonetwork-ui/util/app-config', () => ({ + getThemeConfig: () => ({ + HEADER_BACKGROUND: 'red', + HEADER_FOREGROUND_COLOR: 'white', + }), + getGlobalConfig() { + return { + LANGUAGES: ['en', 'es'], + } + }, +})) + +const searchServiceMock = { + updateFilters: jest.fn(), +} + +class MdViewFacadeMock { + mapApiLinks$ = new BehaviorSubject([]) + geoDataLinks$ = new BehaviorSubject([]) +} + +describe('HeaderRecordComponent', () => { + let component: HeaderOrganizationComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HeaderOrganizationComponent], + imports: [TranslateModule.forRoot()], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: SearchService, useValue: searchServiceMock }, + { + provide: MdViewFacade, + useClass: MdViewFacadeMock, + }, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderOrganizationComponent) + component = fixture.componentInstance + component.metadata = { + ...DATASET_RECORDS[0], + } + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('#back', () => { + it('searchFilter updateSearch', () => { + component.back() + expect(searchServiceMock.updateFilters).toHaveBeenCalledWith({}) + }) + }) +}) diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.ts b/apps/datahub/src/app/organization/header-organization/header-organization.component.ts new file mode 100644 index 0000000000..2680f51c9c --- /dev/null +++ b/apps/datahub/src/app/organization/header-organization/header-organization.component.ts @@ -0,0 +1,45 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { SearchService } from '@geonetwork-ui/feature/search' +import { getGlobalConfig, getThemeConfig } from '@geonetwork-ui/util/app-config' +import { MdViewFacade } from '@geonetwork-ui/feature/record' +import { TranslateModule, TranslateService } from '@ngx-translate/core' +import { UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { UiCatalogModule } from '@geonetwork-ui/ui/catalog' +import { Organization } from '@geonetwork-ui/common/domain/model/record' +import { AsyncPipe, NgIf } from '@angular/common' +import { MatIconModule } from '@angular/material/icon' + +@Component({ + selector: 'datahub-header-organization', + templateUrl: './header-organization.component.html', + styleUrls: ['./header-organization.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + UiInputsModule, + TranslateModule, + UiCatalogModule, + NgIf, + MatIconModule, + AsyncPipe, + ], +}) +export class HeaderOrganizationComponent { + @Input() organization: Organization + + backgroundCss = + getThemeConfig().HEADER_BACKGROUND || + `center /cover url('assets/img/header_bg.webp')` + foregroundColor = getThemeConfig().HEADER_FOREGROUND_COLOR || '#ffffff' + showLanguageSwitcher = getGlobalConfig().LANGUAGES?.length > 0 + + constructor( + private searchService: SearchService, + public facade: MdViewFacade, + private translateService: TranslateService + ) {} + + back() { + this.searchService.updateFilters({}) + } +} diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.css b/apps/datahub/src/app/organization/organization-details/organization-details.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.html b/apps/datahub/src/app/organization/organization-details/organization-details.component.html new file mode 100644 index 0000000000..faa1b3d979 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.html @@ -0,0 +1,61 @@ +
+
+
+

+ {{ organization.description }} +

+
+
+ + + +
+
+
diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts new file mode 100644 index 0000000000..d1ca754499 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts @@ -0,0 +1,666 @@ +import { + Component, + EventEmitter, + Input, + NO_ERRORS_SCHEMA, + Output, +} from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { By } from '@angular/platform-browser' +import { SourcesService } from '@geonetwork-ui/feature/catalog' +import { MapManagerService } from '@geonetwork-ui/feature/map' +import { SearchService } from '@geonetwork-ui/feature/search' +import { + ErrorType, + SearchResultsErrorComponent, +} from '@geonetwork-ui/ui/elements' +import { TranslateModule } from '@ngx-translate/core' +import { BehaviorSubject, of } from 'rxjs' +import { OrganizationDetailsComponent } from './organization-details.component' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { MdViewFacade } from '@geonetwork-ui/feature/record' +import { + CatalogRecord, + DatasetRecord, + DatasetServiceDistribution, + Individual, + Keyword, + Organization, +} from '@geonetwork-ui/common/domain/model/record' + +const SAMPLE_RECORD = { + ...DATASET_RECORDS[0], + extras: { + catalogUuid: 'catalog-0001', + }, +} + +class MdViewFacadeMock { + isPresent$ = new BehaviorSubject(false) + metadata$ = new BehaviorSubject(SAMPLE_RECORD) + mapApiLinks$ = new BehaviorSubject([]) + dataLinks$ = new BehaviorSubject([]) + geoDataLinks$ = new BehaviorSubject([]) + downloadLinks$ = new BehaviorSubject([]) + apiLinks$ = new BehaviorSubject([]) + otherLinks$ = new BehaviorSubject([]) + related$ = new BehaviorSubject(null) + error$ = new BehaviorSubject(null) +} + +class SearchServiceMock { + setFilters = jest.fn() + updateFilters = jest.fn() +} +class SourcesServiceMock { + getSourceLabel = jest.fn(() => of('catalog label')) +} + +class OrganisationsServiceMock { + getFiltersForOrgs = jest.fn((orgs) => + of({ + orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), + }) + ) +} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-map-view', + template: '
', +}) +export class MockDataMapComponent {} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-data-view', + template: '
', +}) +export class MockDataViewComponent {} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-data-view-share', + template: '
', +}) +export class MockDataViewShareComponent {} + +@Component({ + selector: 'datahub-record-downloads', + template: '
', +}) +export class MockDataDownloadsComponent {} + +@Component({ + selector: 'datahub-record-otherlinks', + template: '
', +}) +export class MockDataOtherlinksComponent {} + +@Component({ + selector: 'datahub-record-apis', + template: '
', +}) +export class MockDataApisComponent {} + +@Component({ + selector: 'datahub-record-related-records', + template: '
', +}) +export class MockRelatedComponent {} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-metadata-info', + template: '
', +}) +export class MockMetadataInfoComponent { + @Input() metadata: Partial + @Input() incomplete: boolean + @Output() keyword = new EventEmitter() +} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-metadata-contact', + template: '
', +}) +export class MockMetadataContactComponent { + @Input() metadata: Partial + @Output() organizationClick = new EventEmitter() + @Output() contactClick = new EventEmitter() +} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-metadata-catalog', + template: '
', +}) +export class MockMetadataCatalogComponent { + @Input() sourceLabel: string +} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-record-api-form', + template: '
', +}) +export class MockRecordApiFormComponent { + @Input() apiLink: DatasetServiceDistribution +} +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-image-overlay-preview', + template: '
', +}) +export class MockImgOverlayPreviewComponent { + @Input() imageUrl: string + @Output() isPlaceholderShown = new EventEmitter() +} + +describe('RecordMetadataComponent', () => { + let component: OrganizationDetailsComponent + let fixture: ComponentFixture + let facade + let searchService: SearchService + let sourcesService: SourcesService + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + OrganizationDetailsComponent, + MockDataMapComponent, + MockDataViewComponent, + MockDataViewShareComponent, + MockDataDownloadsComponent, + MockDataOtherlinksComponent, + MockDataApisComponent, + MockRelatedComponent, + SearchResultsErrorComponent, + MockMetadataInfoComponent, + MockMetadataCatalogComponent, + MockMetadataContactComponent, + MockRecordApiFormComponent, + MockImgOverlayPreviewComponent, + ], + schemas: [NO_ERRORS_SCHEMA], + imports: [TranslateModule.forRoot()], + providers: [ + { + provide: MdViewFacade, + useClass: MdViewFacadeMock, + }, + { + provide: MapManagerService, + useValue: {}, + }, + { + provide: SearchService, + useClass: SearchServiceMock, + }, + { + provide: SourcesService, + useClass: SourcesServiceMock, + }, + { + provide: OrganizationsServiceInterface, + useClass: OrganisationsServiceMock, + }, + ], + }).compileComponents() + facade = TestBed.inject(MdViewFacade) + searchService = TestBed.inject(SearchService) + sourcesService = TestBed.inject(SourcesService) + }) + + beforeEach(() => { + fixture = TestBed.createComponent(OrganizationDetailsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('about', () => { + let metadataInfo: MockMetadataInfoComponent + let metadataContact: MockMetadataContactComponent + let catalogComponent: MockMetadataCatalogComponent + + beforeEach(() => { + facade.isPresent$.next(true) + fixture.detectChanges() + metadataInfo = fixture.debugElement.query( + By.directive(MockMetadataInfoComponent) + ).componentInstance + metadataContact = fixture.debugElement.query( + By.directive(MockMetadataContactComponent) + ).componentInstance + catalogComponent = fixture.debugElement.query( + By.directive(MockMetadataCatalogComponent) + ).componentInstance + }) + describe('if metadata present', () => { + it('shows the full metadata', () => { + expect(metadataInfo.metadata).toHaveProperty('abstract') + }) + it('shows the metadata contact', () => { + expect(metadataContact.metadata).toHaveProperty('contacts') + }) + it('shows the metadata catalog', () => { + expect(sourcesService.getSourceLabel).toBeCalledWith( + SAMPLE_RECORD.extras.catalogUuid + ) + expect(catalogComponent.sourceLabel).toEqual('catalog label') + }) + }) + describe('if metadata not present', () => { + beforeEach(() => { + facade.isPresent$.next(false) + fixture.detectChanges() + metadataInfo = fixture.debugElement.query( + By.directive(MockMetadataInfoComponent) + ).componentInstance + }) + it('shows a placeholder', () => { + expect(metadataInfo.metadata).not.toHaveProperty('abstract') + expect(metadataInfo.incomplete).toBeTruthy() + }) + it('does not display the metadata contact component', () => { + expect( + fixture.debugElement.query(By.directive(MockMetadataContactComponent)) + ).toBeFalsy() + }) + it('does not display the metadata catalog component', () => { + expect( + fixture.debugElement.query(By.directive(MockMetadataCatalogComponent)) + ).toBeFalsy() + }) + it('does not display the image overlay preview', () => { + expect( + fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ) + ).toBeFalsy() + }) + }) + describe('Image Overlay Preview', () => { + describe('if metadata without overview', () => { + let imgOverlayPreview: MockImgOverlayPreviewComponent + beforeEach(() => { + facade.isPresent$.next(true) + facade.metadata$.next({}) + fixture.detectChanges() + imgOverlayPreview = fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ).componentInstance + }) + it('should send undefined as imageUrl to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBe(undefined) + }) + }) + describe('if metadata with overview', () => { + let imgOverlayPreview: MockImgOverlayPreviewComponent + beforeEach(() => { + facade.isPresent$.next(true) + fixture.detectChanges() + imgOverlayPreview = fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ).componentInstance + }) + describe('and url defined', () => { + it('should send the imageUrl to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBeDefined() + }) + }) + describe('and url undefined', () => { + beforeEach(() => { + facade.metadata$.next({ overviews: [] }) + fixture.detectChanges() + }) + it('should send the imagUrl as null to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBeNull() + }) + }) + }) + }) + }) + + describe('Preview', () => { + describe('when no MAPAPI, GEODATA nor DATA link', () => { + beforeEach(() => { + fixture.detectChanges() + }) + it('does not render preview content', () => { + expect(fixture.debugElement.query(By.css('#preview'))).toBeFalsy() + }) + }) + }) + describe('Map view', () => { + let mapTab + let tabGroup + describe('when DATA link, but no MAPAPI and no GEODATA link', () => { + beforeEach(() => { + facade.dataLinks$.next(['link']) + fixture.detectChanges() + mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] + tabGroup = fixture.debugElement.queryAll(By.css('mat-tab-group'))[0] + }) + it('renders preview, map tab is disabled', () => { + expect(mapTab.nativeNode.disabled).toBe(true) + }) + it('renders preview, table tab is selected', () => { + expect(tabGroup.nativeNode.selectedIndex).toBe(1) + }) + it('does not render map component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataMapComponent)) + ).toBeFalsy() + }) + }) + describe('when a MAPAPI link present', () => { + beforeEach(() => { + facade.mapApiLinks$.next(['link']) + fixture.detectChanges() + mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] + }) + it('renders preview, map tab is enabled', () => { + expect(mapTab.nativeNode.disabled).toBe(false) + }) + it('renders map component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataMapComponent)) + ).toBeTruthy() + }) + }) + describe('when a GEODATA link present', () => { + beforeEach(() => { + facade.geoDataLinks$.next(['link']) + fixture.detectChanges() + mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] + }) + it('renders preview, map tab is enabled', () => { + expect(mapTab.nativeNode.disabled).toBe(false) + }) + it('renders map component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataMapComponent)) + ).toBeTruthy() + }) + }) + }) + describe('Data view - table and chart', () => { + let tableTab + let chartTab + let tabGroup + describe('when MAPAPI link, but no DATA and no GEODATA link', () => { + beforeEach(() => { + facade.mapApiLinks$.next(['link']) + facade.dataLinks$.next(null) + facade.geoDataLinks$.next(null) + fixture.detectChanges() + tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] + chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] + tabGroup = fixture.debugElement.queryAll(By.css('mat-tab-group'))[0] + }) + it('renders preview, table tab is disabled', () => { + expect(tableTab.nativeNode.disabled).toBe(true) + }) + it('renders preview, chart tab is disabled', () => { + expect(chartTab.nativeNode.disabled).toBe(true) + }) + it('renders preview, map tab is selected', () => { + expect(tabGroup.nativeNode.selectedIndex).toBe(0) + }) + it('does not render any data view component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataViewComponent)) + ).toBeFalsy() + }) + it('does not render the permalink component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataViewShareComponent)) + ).toBeFalsy() + }) + }) + describe('when a DATA link present', () => { + beforeEach(() => { + facade.dataLinks$.next(['link']) + fixture.detectChanges() + tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] + chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] + }) + it('renders preview, table tab is enabled', () => { + expect(tableTab.nativeNode.disabled).toBe(false) + }) + it('renders preview, chart tab is enabled', () => { + expect(chartTab.nativeNode.disabled).toBe(false) + }) + it('renders two data view components (for table and chart tabs)', () => { + expect( + fixture.debugElement.queryAll(By.directive(MockDataViewComponent)) + .length + ).toEqual(2) + }) + it('does not render the permalink component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataViewShareComponent)) + ).toBeFalsy() + }) + describe('when selectedTabIndex$ is 2 (chart tab)', () => { + beforeEach(() => { + component.selectedTabIndex$.next(2) + fixture.detectChanges() + }) + it('renders the permalink component', () => { + expect( + fixture.debugElement.query(By.directive(MockDataViewShareComponent)) + ).toBeTruthy() + }) + }) + }) + describe('when a GEODATA link present', () => { + beforeEach(() => { + facade.geoDataLinks$.next(['link']) + fixture.detectChanges() + tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] + chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] + }) + it('renders preview, table tab is enabled', () => { + expect(tableTab.nativeNode.disabled).toBe(false) + }) + it('renders preview, chart tab is enabled', () => { + expect(chartTab.nativeNode.disabled).toBe(false) + }) + it('renders two data view components (for table and chart tabs)', () => { + expect( + fixture.debugElement.queryAll(By.directive(MockDataViewComponent)) + .length + ).toEqual(2) + }) + }) + }) + describe('Downloads', () => { + let downloadsComponent + describe('when no DOWNLOAD link', () => { + beforeEach(() => { + fixture.detectChanges() + downloadsComponent = fixture.debugElement.query( + By.directive(MockDataDownloadsComponent) + ) + }) + it('download component does not render', () => { + expect(downloadsComponent).toBeFalsy() + }) + }) + describe('when DOWNLOAD link', () => { + beforeEach(() => { + facade.downloadLinks$.next(['link']) + fixture.detectChanges() + downloadsComponent = fixture.debugElement.query( + By.directive(MockDataDownloadsComponent) + ) + }) + it('download component renders', () => { + expect(downloadsComponent).toBeTruthy() + }) + }) + }) + describe('Otherlinks', () => { + let otherLinksComponent + describe('when no OTHER link', () => { + beforeEach(() => { + fixture.detectChanges() + otherLinksComponent = fixture.debugElement.query( + By.directive(MockDataOtherlinksComponent) + ) + }) + it('otherlink component does not render', () => { + expect(otherLinksComponent).toBeFalsy() + }) + }) + describe('when OTHER link', () => { + beforeEach(() => { + facade.otherLinks$.next(['link']) + fixture.detectChanges() + otherLinksComponent = fixture.debugElement.query( + By.directive(MockDataOtherlinksComponent) + ) + }) + it('otherlink component renders', () => { + expect(otherLinksComponent).toBeTruthy() + }) + }) + }) + describe('API', () => { + let apiComponent + describe('when no API link', () => { + beforeEach(() => { + fixture.detectChanges() + apiComponent = fixture.debugElement.query( + By.directive(MockDataApisComponent) + ) + }) + it('API component does not render', () => { + expect(apiComponent).toBeFalsy() + }) + }) + describe('when API link', () => { + beforeEach(() => { + facade.apiLinks$.next(['link']) + fixture.detectChanges() + apiComponent = fixture.debugElement.query( + By.directive(MockDataApisComponent) + ) + }) + it('API component renders', () => { + expect(apiComponent).toBeTruthy() + }) + }) + }) + + describe('related records', () => { + let relatedComponent + describe('when no related records', () => { + beforeEach(() => { + facade.related$.next([]) + fixture.detectChanges() + relatedComponent = fixture.debugElement.query( + By.directive(MockRelatedComponent) + ) + }) + it('Related component does not render', () => { + expect(relatedComponent).toBeFalsy() + }) + }) + describe('when related records', () => { + beforeEach(() => { + facade.related$.next([{ title: 'title' }]) + fixture.detectChanges() + relatedComponent = fixture.debugElement.query( + By.directive(MockRelatedComponent) + ) + }) + it('Related component renders', () => { + expect(relatedComponent).toBeTruthy() + }) + }) + }) + + describe('#onInfoKeywordClick', () => { + it('call searchService for any', () => { + component.onInfoKeywordClick({ + thesaurus: { id: 'geonetwork.thesaurus.local' }, + type: 'other', + label: 'international', + }) + expect(searchService.updateFilters).toHaveBeenCalledWith({ + any: 'international', + }) + }) + }) + describe('#onContactClick', () => { + it('call update search for OrgForResource', () => { + component.onOrganizationClick({ + name: 'MyOrganization', + website: new URL('https://www.my.org/info'), + logoUrl: new URL('https://www.my.org/logo.png'), + description: 'A generic organization', + }) + expect(searchService.updateFilters).toHaveBeenCalledWith({ + orgs: { + MyOrganization: true, + }, + }) + }) + }) + + describe('error handling', () => { + describe('normal', () => { + it('does not show errors', () => { + const result = fixture.debugElement.query( + By.directive(SearchResultsErrorComponent) + ) + expect(result).toBeFalsy() + }) + }) + describe('record not found', () => { + beforeEach(() => { + facade.error$.next({ notFound: true }) + fixture.detectChanges() + }) + it('shows error', () => { + const result = fixture.debugElement.query( + By.directive(SearchResultsErrorComponent) + ) + + expect(result).toBeTruthy() + expect(result.componentInstance.type).toBe(ErrorType.RECORD_NOT_FOUND) + expect(result.componentInstance.error).toBe(undefined) + expect(result.componentInstance.recordId).toBe( + SAMPLE_RECORD.uniqueIdentifier + ) + }) + }) + describe('other error', () => { + beforeEach(() => { + facade.error$.next({ otherError: 'This is an Error!' }) + fixture.detectChanges() + }) + it('shows error', () => { + const result = fixture.debugElement.query( + By.directive(SearchResultsErrorComponent) + ) + + expect(result).toBeTruthy() + expect(result.componentInstance.type).toBe(ErrorType.RECEIVED_ERROR) + expect(result.componentInstance.error).toBe('This is an Error!') + }) + }) + }) +}) diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts new file mode 100644 index 0000000000..b41d1207c1 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts @@ -0,0 +1,28 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + SimpleChanges, +} from '@angular/core' +import { AsyncPipe, NgIf } from '@angular/common' +import { Organization } from '@geonetwork-ui/common/domain/model/record' +import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { MatIconModule } from '@angular/material/icon' +import { TranslateModule } from '@ngx-translate/core' + +@Component({ + selector: 'datahub-organization-details', + templateUrl: './organization-details.component.html', + styleUrls: ['./organization-details.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [AsyncPipe, NgIf, ButtonComponent, MatIconModule, TranslateModule], +}) +export class OrganizationDetailsComponent implements OnChanges { + @Input() organization: Organization + + ngOnChanges(changes: SimpleChanges): void { + console.log(changes['organization']) + } +} diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.css b/apps/datahub/src/app/organization/organization-page/organization-page.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.html b/apps/datahub/src/app/organization/organization-page/organization-page.component.html new file mode 100644 index 0000000000..8af1034ceb --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts b/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts new file mode 100644 index 0000000000..e719f1e097 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts @@ -0,0 +1,74 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { OrganisationsPageComponent } from './organisations-page.component' +import { SearchService } from '@geonetwork-ui/feature/search' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { of } from 'rxjs' + +class SearchServiceMock { + setFilters = jest.fn() +} + +class OrganisationsServiceMock { + getFiltersForOrgs = jest.fn((orgs) => + of({ + orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), + }) + ) +} + +describe('OrganisationsPageComponent', () => { + let component: OrganisationsPageComponent + let fixture: ComponentFixture + let searchService: SearchService + let orgsService: OrganizationsServiceInterface + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OrganisationsPageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: SearchService, + useClass: SearchServiceMock, + }, + { + provide: OrganizationsServiceInterface, + useClass: OrganisationsServiceMock, + }, + ], + }).compileComponents() + + searchService = TestBed.inject(SearchService) + orgsService = TestBed.inject(OrganizationsServiceInterface) + + fixture = TestBed.createComponent(OrganisationsPageComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('#searchByOrganisation', () => { + beforeEach(() => { + component.searchByOrganisation({ + name: 'MyOrg', + }) + }) + it('generates filters for the org', () => { + expect(orgsService.getFiltersForOrgs).toHaveBeenCalledWith([ + { name: 'MyOrg' }, + ]) + }) + it('updates filters to filter on the org', () => { + expect(searchService.setFilters).toHaveBeenCalledWith({ + orgs: { + MyOrg: true, + }, + }) + }) + }) +}) diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.ts b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts new file mode 100644 index 0000000000..ce9077c94e --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' +import { RouterFacade } from '@geonetwork-ui/feature/router' +import { AsyncPipe } from '@angular/common' +import { HeaderOrganizationComponent } from '../header-organization/header-organization.component' +import { OrganizationDetailsComponent } from '../organization-details/organization-details.component' +import { combineLatest, Observable, of, switchMap } from 'rxjs' +import { filter } from 'rxjs/operators' +import { Organization } from '@geonetwork-ui/common/domain/model/record' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { LetDirective } from '@ngrx/component' + +@Component({ + selector: 'datahub-organization-page', + templateUrl: './organization-page.component.html', + styleUrls: ['./organization-page.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + AsyncPipe, + HeaderOrganizationComponent, + OrganizationDetailsComponent, + LetDirective, + ], +}) +export class OrganizationPageComponent implements OnInit { + organization$: Observable + + constructor( + private router: RouterFacade, + private orgService: OrganizationsServiceInterface + ) {} + + ngOnInit(): void { + this.organization$ = combineLatest([ + this.router.pathParams$, + this.orgService.organisations$, + ]).pipe( + filter(([pathParams, _]) => Object.keys(pathParams).length > 0), + switchMap(([pathParams, organizations]) => + of( + organizations.find( + (organization) => organization.name === pathParams['name'] + ) + ) + ) + ) + } +} diff --git a/apps/datahub/src/app/router/datahub-router.service.ts b/apps/datahub/src/app/router/datahub-router.service.ts index 22f50097b2..0f6c7d58a0 100644 --- a/apps/datahub/src/app/router/datahub-router.service.ts +++ b/apps/datahub/src/app/router/datahub-router.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core' import { Router, Routes } from '@angular/router' import { - ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, } from '@geonetwork-ui/feature/router' import { HomePageComponent } from '../home/home-page/home-page.component' @@ -14,6 +14,7 @@ import { ROUTER_ROUTE_NEWS, ROUTER_ROUTE_ORGANISATIONS, } from './constants' +import { OrganizationPageComponent } from '../organization/organization-page/organization-page.component' @Injectable({ providedIn: 'root', @@ -64,13 +65,20 @@ export class DatahubRouterService { data: { shouldDetach: true, }, - }, + } ], }, { path: `${ROUTER_ROUTE_DATASET}/:metadataUuid`, component: RecordPageComponent, }, + { + path: `${ROUTER_ROUTE_ORGANIZATION}/:name`, + component: OrganizationPageComponent, + data: { + shouldDetach: true, + }, + }, { path: '**', redirectTo: '', pathMatch: 'full' }, ] } @@ -78,4 +86,8 @@ export class DatahubRouterService { getSearchRoute(): string { return `${ROUTER_ROUTE_HOME}/${ROUTER_ROUTE_SEARCH}` } + + getOrganizationPageRoute(): string { + return ROUTER_ROUTE_ORGANIZATION + } } diff --git a/apps/datahub/src/styles.css b/apps/datahub/src/styles.css index f290ad9d70..245c168a2d 100644 --- a/apps/datahub/src/styles.css +++ b/apps/datahub/src/styles.css @@ -16,6 +16,9 @@ body { .container-lg { max-width: 1024px; } +.container-xl { + max-width: 1440px; +} html.record-page-active { scroll-padding-top: 220px; diff --git a/libs/common/domain/src/lib/model/record/organization.model.ts b/libs/common/domain/src/lib/model/record/organization.model.ts index fa387137b0..8db5ed1a7b 100644 --- a/libs/common/domain/src/lib/model/record/organization.model.ts +++ b/libs/common/domain/src/lib/model/record/organization.model.ts @@ -1,5 +1,6 @@ export interface Organization { name: string + email?: string description?: string website?: URL logoUrl?: URL diff --git a/libs/feature/catalog/src/lib/feature-catalog.module.ts b/libs/feature/catalog/src/lib/feature-catalog.module.ts index c3f2fd8265..7c97c0d726 100644 --- a/libs/feature/catalog/src/lib/feature-catalog.module.ts +++ b/libs/feature/catalog/src/lib/feature-catalog.module.ts @@ -4,7 +4,6 @@ import { UiCatalogModule } from '@geonetwork-ui/ui/catalog' import { GroupsApiService, SearchApiService, - SiteApiService, } from '@geonetwork-ui/data-access/gn4' import { CommonModule } from '@angular/common' import { SourceLabelComponent } from './source-label/source-label.component' diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.ts b/libs/feature/catalog/src/lib/organisations/organisations.component.ts index 436345abe2..84194910c4 100644 --- a/libs/feature/catalog/src/lib/organisations/organisations.component.ts +++ b/libs/feature/catalog/src/lib/organisations/organisations.component.ts @@ -10,10 +10,10 @@ import { import { Organization } from '@geonetwork-ui/common/domain/model/record' import { BehaviorSubject, combineLatest, Observable } from 'rxjs' import { map, startWith, tap } from 'rxjs/operators' -import { ORGANIZATION_URL_TOKEN } from '../feature-catalog.module' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { SortByField } from '@geonetwork-ui/common/domain/model/search' import { createFuzzyFilter } from '@geonetwork-ui/util/shared' +import { ORGANIZATION_PAGE_URL_TOKEN } from '../organization-url.token' @Component({ selector: 'gn-ui-organisations', @@ -28,7 +28,7 @@ export class OrganisationsComponent { constructor( private organisationsService: OrganizationsServiceInterface, @Optional() - @Inject(ORGANIZATION_URL_TOKEN) + @Inject(ORGANIZATION_PAGE_URL_TOKEN) private urlTemplate: string ) {} diff --git a/libs/feature/catalog/src/lib/organization-url.token.ts b/libs/feature/catalog/src/lib/organization-url.token.ts new file mode 100644 index 0000000000..b3f5739b26 --- /dev/null +++ b/libs/feature/catalog/src/lib/organization-url.token.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core' + +// expects the replacement key ${name} +export const ORGANIZATION_PAGE_URL_TOKEN = new InjectionToken('organization-page-url-token') diff --git a/libs/feature/router/src/lib/default/constants.ts b/libs/feature/router/src/lib/default/constants.ts index d446911cf9..3365b3eca5 100644 --- a/libs/feature/router/src/lib/default/constants.ts +++ b/libs/feature/router/src/lib/default/constants.ts @@ -2,6 +2,7 @@ export const ROUTER_STATE_KEY = 'router' export const ROUTER_ROUTE_SEARCH = 'search' export const ROUTER_ROUTE_DATASET = 'dataset' +export const ROUTER_ROUTE_ORGANIZATION = 'organization' export enum ROUTE_PARAMS { SORT = '_sort', diff --git a/libs/feature/router/src/lib/default/router.config.ts b/libs/feature/router/src/lib/default/router.config.ts index 3900710e7f..63227fa12f 100644 --- a/libs/feature/router/src/lib/default/router.config.ts +++ b/libs/feature/router/src/lib/default/router.config.ts @@ -4,6 +4,7 @@ export interface RouterConfigModel { searchStateId: string searchRouteComponent: Type recordRouteComponent: Type + organizationRouteComponent: Type } export const ROUTER_CONFIG = new InjectionToken( diff --git a/libs/feature/router/src/lib/default/router.service.ts b/libs/feature/router/src/lib/default/router.service.ts index 6020fcc566..19c1f41fb9 100644 --- a/libs/feature/router/src/lib/default/router.service.ts +++ b/libs/feature/router/src/lib/default/router.service.ts @@ -1,5 +1,9 @@ import { Inject, Injectable } from '@angular/core' -import { ROUTER_ROUTE_DATASET, ROUTER_ROUTE_SEARCH } from '.' +import { + ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_ORGANIZATION, + ROUTER_ROUTE_SEARCH, +} from '.' import { Router, Routes } from '@angular/router' import { ROUTER_CONFIG, RouterConfigModel } from './router.config' @@ -30,10 +34,18 @@ export class RouterService { path: `${ROUTER_ROUTE_DATASET}/:metadataUuid`, component: this.routerConfig.recordRouteComponent, }, + { + path: `${ROUTER_ROUTE_ORGANIZATION}/:name`, + component: this.routerConfig.organizationRouteComponent, + }, ] } getSearchRoute(): string { return ROUTER_ROUTE_SEARCH } + + getOrganizationPageRoute(): string { + return ROUTER_ROUTE_ORGANIZATION + } } diff --git a/libs/feature/router/src/lib/default/state/router.facade.ts b/libs/feature/router/src/lib/default/state/router.facade.ts index a8e6ddfe40..c2f9dd2054 100644 --- a/libs/feature/router/src/lib/default/state/router.facade.ts +++ b/libs/feature/router/src/lib/default/state/router.facade.ts @@ -3,7 +3,7 @@ import { MdViewActions } from '@geonetwork-ui/feature/record' import { RouterService } from '../router.service' import { RouterReducerState } from '@ngrx/router-store' import { select, Store } from '@ngrx/store' -import { distinctUntilChanged, filter, map, take } from 'rxjs/operators' +import { distinctUntilChanged, filter, map, take, tap } from 'rxjs/operators' import { ROUTER_ROUTE_DATASET, ROUTER_ROUTE_SEARCH, @@ -51,6 +51,14 @@ export class RouterFacade { }) } + goToOrganization(organizationName: string) { + const path = `${this.routerService.getOrganizationPageRoute()}/${organizationName}` + this.go({ + path, + queryParamsHandling: '', + }) + } + updateSearch(query?: SearchRouteParams) { this.go({ path: this.routerService.getSearchRoute(), diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts index 0cc798faab..b9819e4323 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts @@ -18,7 +18,7 @@ export class OrganisationPreviewComponent { @Input() organisationUrl: string @Output() clickedOrganisation = new EventEmitter() - clickOrganisation(event: Event) { + clickOrganisation(event: Event) { event.preventDefault() this.clickedOrganisation.emit(this.organisation) } diff --git a/libs/ui/catalog/src/lib/ui-catalog.module.ts b/libs/ui/catalog/src/lib/ui-catalog.module.ts index 990100a7f3..6a9a1fdfdd 100644 --- a/libs/ui/catalog/src/lib/ui-catalog.module.ts +++ b/libs/ui/catalog/src/lib/ui-catalog.module.ts @@ -9,6 +9,7 @@ import { OrganisationsFilterComponent } from './organisations-filter/organisatio import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { LanguageSwitcherComponent } from './language-switcher/language-switcher.component' import { OrganisationsResultComponent } from './organisations-result/organisations-result.component' +import { RouterLink } from '@angular/router' @NgModule({ declarations: [ @@ -24,6 +25,7 @@ import { OrganisationsResultComponent } from './organisations-result/organisatio UiElementsModule, UiInputsModule, MatIconModule, + RouterLink, ], exports: [ CatalogTitleComponent, diff --git a/libs/ui/elements/src/lib/error/error.component.stories.ts b/libs/ui/elements/src/lib/error/error.component.stories.ts index 0e53bc15c5..37bca03462 100644 --- a/libs/ui/elements/src/lib/error/error.component.stories.ts +++ b/libs/ui/elements/src/lib/error/error.component.stories.ts @@ -41,7 +41,7 @@ export const Primary: StoryObj = { error: 'something wrong happened', recordId: 'thisIsAnID', }, - argTypes: { + argTypes: {v type: { control: 'radio', options: [ diff --git a/translations/en.json b/translations/en.json index 53e231e70e..c3ed62929c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "Name Z → A", "organisations.sortBy.recordCountAsc": "Publications 0 → 9", "organisations.sortBy.recordCountDesc": "Publications 9 → 0", + "organization.header.recordCount": "{count, plural, =0{data} one{data} other{datas}}", + "organization.details.publishedDataset": "{count, plural, =0{published dataset} one{published dataset} other{published datasets}}", + "organization.details.mailContact": "Contact by email", "pagination.nextPage": "Next page", "pagination.page": "page", "pagination.pageOf": "of", diff --git a/translations/es.json b/translations/es.json index 384aa3cba3..8fb135e73c 100644 --- a/translations/es.json +++ b/translations/es.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "", "organisations.sortBy.recordCountAsc": "", "organisations.sortBy.recordCountDesc": "", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{{}}", + "organization.details.mailContact": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", diff --git a/translations/fr.json b/translations/fr.json index 8dc865f38f..eab44644cb 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "Nom Z → A", "organisations.sortBy.recordCountAsc": "Données 0 → 9", "organisations.sortBy.recordCountDesc": "Données 9 → 0", + "organization.header.recordCount": "{count, plural, =0{donnée} one{donnée} other{données}}", + "organization.details.publishedDataset": "{count, plural, =0{donnée publiée} one{donnée publiée} other{données publiées}}", + "organization.details.mailContact": "Contacter par mail", "pagination.nextPage": "Page suivante", "pagination.page": "page", "pagination.pageOf": "sur", diff --git a/translations/it.json b/translations/it.json index 34ec4c3674..b579543d57 100644 --- a/translations/it.json +++ b/translations/it.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "Nome Z → A", "organisations.sortBy.recordCountAsc": "Dati 0 → 9", "organisations.sortBy.recordCountDesc": "Dati 9 → 0", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{{}}", + "organization.details.mailContact": "", "pagination.nextPage": "Pagina successiva", "pagination.page": "pagina", "pagination.pageOf": "di", diff --git a/translations/nl.json b/translations/nl.json index 98078f8718..699aef495b 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "", "organisations.sortBy.recordCountAsc": "", "organisations.sortBy.recordCountDesc": "", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{{}}", + "organization.details.mailContact": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", diff --git a/translations/pt.json b/translations/pt.json index 46a680eb6e..f405a198e1 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "", "organisations.sortBy.recordCountAsc": "", "organisations.sortBy.recordCountDesc": "", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{{}}", + "organization.details.mailContact": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", diff --git a/translations/sk.json b/translations/sk.json index 02131776ed..8bb1864411 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -240,6 +240,9 @@ "organisations.sortBy.nameDesc": "Názov Z → A", "organisations.sortBy.recordCountAsc": "Publikácie 0 → 9", "organisations.sortBy.recordCountDesc": "Publikácie 9 → 0", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{{}}", + "organization.details.mailContact": "", "pagination.nextPage": "Ďalšia stránka", "pagination.page": "strana", "pagination.pageOf": "z", From d5c5aa02a388d13b090148358559e004960e1acb Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 17 May 2024 13:53:24 +0200 Subject: [PATCH 02/10] refactor(ui): moved MaxLineComponent to layout and made it standalone. --- libs/ui/elements/src/index.ts | 1 - .../src/lib/error/error.component.stories.ts | 2 +- libs/ui/elements/src/lib/ui-elements.module.ts | 3 --- libs/ui/layout/src/index.ts | 1 + .../src/lib/max-lines/max-lines.component.css | 0 .../src/lib/max-lines/max-lines.component.html | 0 .../src/lib/max-lines/max-lines.component.spec.ts | 0 .../lib/max-lines/max-lines.component.stories.ts | 8 ++++---- .../src/lib/max-lines/max-lines.component.ts | 14 +++++++++----- libs/ui/layout/src/lib/ui-layout.module.ts | 1 - 10 files changed, 15 insertions(+), 15 deletions(-) rename libs/ui/{elements => layout}/src/lib/max-lines/max-lines.component.css (100%) rename libs/ui/{elements => layout}/src/lib/max-lines/max-lines.component.html (100%) rename libs/ui/{elements => layout}/src/lib/max-lines/max-lines.component.spec.ts (100%) rename libs/ui/{elements => layout}/src/lib/max-lines/max-lines.component.stories.ts (93%) rename libs/ui/{elements => layout}/src/lib/max-lines/max-lines.component.ts (93%) diff --git a/libs/ui/elements/src/index.ts b/libs/ui/elements/src/index.ts index e48900c513..5ac80a86d9 100644 --- a/libs/ui/elements/src/index.ts +++ b/libs/ui/elements/src/index.ts @@ -7,7 +7,6 @@ export * from './lib/image-overlay-preview/image-overlay-preview.component' export * from './lib/link-card/link-card.component' export * from './lib/markdown-editor/markdown-editor.component' export * from './lib/markdown-parser/markdown-parser.component' -export * from './lib/max-lines/max-lines.component' export * from './lib/metadata-catalog/metadata-catalog.component' export * from './lib/metadata-contact/metadata-contact.component' export * from './lib/metadata-info/metadata-info.component' diff --git a/libs/ui/elements/src/lib/error/error.component.stories.ts b/libs/ui/elements/src/lib/error/error.component.stories.ts index 37bca03462..0e53bc15c5 100644 --- a/libs/ui/elements/src/lib/error/error.component.stories.ts +++ b/libs/ui/elements/src/lib/error/error.component.stories.ts @@ -41,7 +41,7 @@ export const Primary: StoryObj = { error: 'something wrong happened', recordId: 'thisIsAnID', }, - argTypes: {v + argTypes: { type: { control: 'radio', options: [ diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index 336175d9b0..acee63b516 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -26,7 +26,6 @@ import { AvatarComponent } from './avatar/avatar.component' import { UserPreviewComponent } from './user-preview/user-preview.component' import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' import { PaginationButtonsComponent } from './pagination-buttons/pagination-buttons.component' -import { MaxLinesComponent } from './max-lines/max-lines.component' import { RecordApiFormComponent } from './record-api-form/record-api-form.component' import { MarkdownParserComponent } from './markdown-parser/markdown-parser.component' import { ImageOverlayPreviewComponent } from './image-overlay-preview/image-overlay-preview.component' @@ -68,7 +67,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe' UserPreviewComponent, GnUiLinkifyDirective, PaginationButtonsComponent, - MaxLinesComponent, RecordApiFormComponent, UserFeedbackItemComponent, ImageOverlayPreviewComponent, @@ -90,7 +88,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe' AvatarComponent, UserPreviewComponent, PaginationButtonsComponent, - MaxLinesComponent, RecordApiFormComponent, MarkdownParserComponent, UserFeedbackItemComponent, diff --git a/libs/ui/layout/src/index.ts b/libs/ui/layout/src/index.ts index 358c081b9f..ee72bfe6cb 100644 --- a/libs/ui/layout/src/index.ts +++ b/libs/ui/layout/src/index.ts @@ -3,6 +3,7 @@ export * from './lib/carousel/carousel.component' export * from './lib/expandable-panel-button/expandable-panel-button.component' export * from './lib/expandable-panel/expandable-panel.component' export * from './lib/form-field-wrapper/form-field-wrapper.component' +export * from './lib/max-lines/max-lines.component' export * from './lib/interactive-table/interactive-table-column/interactive-table-column.component' export * from './lib/interactive-table/interactive-table.component' export * from './lib/sticky-header/sticky-header.component' diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.css b/libs/ui/layout/src/lib/max-lines/max-lines.component.css similarity index 100% rename from libs/ui/elements/src/lib/max-lines/max-lines.component.css rename to libs/ui/layout/src/lib/max-lines/max-lines.component.css diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.html b/libs/ui/layout/src/lib/max-lines/max-lines.component.html similarity index 100% rename from libs/ui/elements/src/lib/max-lines/max-lines.component.html rename to libs/ui/layout/src/lib/max-lines/max-lines.component.html diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.spec.ts b/libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts similarity index 100% rename from libs/ui/elements/src/lib/max-lines/max-lines.component.spec.ts rename to libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.stories.ts b/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts similarity index 93% rename from libs/ui/elements/src/lib/max-lines/max-lines.component.stories.ts rename to libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts index 6da53e4f6f..c93ce310e2 100644 --- a/libs/ui/elements/src/lib/max-lines/max-lines.component.stories.ts +++ b/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts @@ -10,16 +10,16 @@ import { TranslateModule } from '@ngx-translate/core' import { TRANSLATE_DEFAULT_CONFIG, UtilI18nModule, -} from '@geonetwork-ui/util/i18n' +} from '../../../../../util/i18n/src' import { importProvidersFrom } from '@angular/core' export default { - title: 'Elements/MaxLinesComponent', + title: 'Layout/MaxLinesComponent', component: MaxLinesComponent, decorators: [ moduleMetadata({ - declarations: [MaxLinesComponent], - imports: [TranslateModule], + declarations: [], + imports: [TranslateModule, MaxLinesComponent], }), applicationConfig({ providers: [ diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.ts b/libs/ui/layout/src/lib/max-lines/max-lines.component.ts similarity index 93% rename from libs/ui/elements/src/lib/max-lines/max-lines.component.ts rename to libs/ui/layout/src/lib/max-lines/max-lines.component.ts index 1d47058644..702781f902 100644 --- a/libs/ui/elements/src/lib/max-lines/max-lines.component.ts +++ b/libs/ui/layout/src/lib/max-lines/max-lines.component.ts @@ -1,19 +1,23 @@ import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, - Input, ElementRef, - ChangeDetectionStrategy, - AfterViewInit, - ViewChild, + Input, OnDestroy, - ChangeDetectorRef, + ViewChild, } from '@angular/core' +import { CommonModule } from '@angular/common' +import { TranslateModule } from '@ngx-translate/core' @Component({ selector: 'gn-ui-max-lines', templateUrl: './max-lines.component.html', styleUrls: ['./max-lines.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [CommonModule, TranslateModule], }) export class MaxLinesComponent implements AfterViewInit, OnDestroy { @Input() maxLines = 6 diff --git a/libs/ui/layout/src/lib/ui-layout.module.ts b/libs/ui/layout/src/lib/ui-layout.module.ts index 5d22fc9ce9..4cc50c09b6 100644 --- a/libs/ui/layout/src/lib/ui-layout.module.ts +++ b/libs/ui/layout/src/lib/ui-layout.module.ts @@ -6,7 +6,6 @@ import { StickyHeaderComponent } from './sticky-header/sticky-header.component' import { AnchorLinkDirective } from './anchor-link/anchor-link.directive' import { ExpandablePanelButtonComponent } from './expandable-panel-button/expandable-panel-button.component' import { MatIconModule } from '@angular/material/icon' -import { CarouselComponent } from './carousel/carousel.component' @NgModule({ imports: [CommonModule, MatIconModule, TranslateModule.forChild()], From a13fe244fd2ab49628c0b621e7e7ebc105ce21df Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Thu, 23 May 2024 10:07:06 +0200 Subject: [PATCH 03/10] refactor(datahub): fix some of the 'organization' typos. --- .../navigation-menu/navigation-menu.component.spec.ts | 4 ++-- .../home/navigation-menu/navigation-menu.component.ts | 4 ++-- .../news-page/key-figures/key-figures.component.ts | 4 ++-- apps/datahub/src/app/router/constants.ts | 2 +- apps/datahub/src/app/router/datahub-router.service.ts | 9 +++++---- .../src/lib/gn4/platform/gn4-platform.mapper.ts | 2 +- libs/common/domain/src/lib/model/user/user.model.ts | 2 +- .../gn4/src/openapi/model/user.api.model.ts | 2 +- .../catalog/src/lib/my-org/my-org.service.spec.ts | 6 +++--- libs/feature/catalog/src/lib/my-org/my-org.service.ts | 4 ++-- .../src/lib/organisations/organisations.component.html | 2 +- .../organisation-preview.component.html | 10 +++++----- .../organisation-preview.component.spec.ts | 2 +- .../organisation-preview.component.stories.ts | 2 +- .../organisation-preview.component.ts | 4 ++-- 15 files changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts index 7abef2b1c8..29e3bafb81 100644 --- a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts +++ b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts @@ -9,7 +9,7 @@ import { readFirst } from '@nx/angular/testing' import { BehaviorSubject } from 'rxjs' import { ROUTER_ROUTE_NEWS, - ROUTER_ROUTE_ORGANISATIONS, + ROUTER_ROUTE_ORGANIZATIONS, } from '../../router/constants' import { NavigationMenuComponent } from './navigation-menu.component' @@ -73,7 +73,7 @@ describe('NavigationMenuComponent', () => { describe('navigate to organisations route', () => { beforeEach(() => { routerFacadeMock.currentRoute$.next({ - url: [{ path: ROUTER_ROUTE_ORGANISATIONS }], + url: [{ path: ROUTER_ROUTE_ORGANIZATIONS }], }) }) it('displays activeLabel for organisations', async () => { diff --git a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts index 5523592a03..3aa35c9e18 100644 --- a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts +++ b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts @@ -7,7 +7,7 @@ import { import { map } from 'rxjs/operators' import { ROUTER_ROUTE_NEWS, - ROUTER_ROUTE_ORGANISATIONS, + ROUTER_ROUTE_ORGANIZATIONS, } from '../../router/constants' import { getThemeConfig } from '@geonetwork-ui/util/app-config' @@ -33,7 +33,7 @@ export class NavigationMenuComponent { label: 'datahub.header.datasets', }, { - link: `${ROUTER_ROUTE_ORGANISATIONS}`, + link: `${ROUTER_ROUTE_ORGANIZATIONS}`, label: 'datahub.header.organisations', }, ] diff --git a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.ts b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.ts index bee90f8cfe..30880371a7 100644 --- a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.ts +++ b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { startWith } from 'rxjs/operators' import { RecordsService } from '@geonetwork-ui/feature/catalog' import { ROUTER_ROUTE_SEARCH } from '@geonetwork-ui/feature/router' -import { ROUTER_ROUTE_ORGANISATIONS } from '../../../router/constants' +import { ROUTER_ROUTE_ORGANIZATIONS } from '../../../router/constants' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { marker } from '@biesbjerg/ngx-translate-extract-marker' @@ -19,7 +19,7 @@ export class KeyFiguresComponent { recordsCount$ = this.catalogRecords.recordsCount$.pipe(startWith('-')) orgsCount$ = this.catalogOrgs.organisationsCount$.pipe(startWith('-')) ROUTE_SEARCH = `/${ROUTER_ROUTE_SEARCH}` - ROUTE_ORGANISATIONS = `/${ROUTER_ROUTE_ORGANISATIONS}` + ROUTE_ORGANISATIONS = `/${ROUTER_ROUTE_ORGANIZATIONS}` constructor( private catalogRecords: RecordsService, diff --git a/apps/datahub/src/app/router/constants.ts b/apps/datahub/src/app/router/constants.ts index 2c962b5eca..234beacd1e 100644 --- a/apps/datahub/src/app/router/constants.ts +++ b/apps/datahub/src/app/router/constants.ts @@ -1,3 +1,3 @@ export const ROUTER_ROUTE_HOME = 'home' export const ROUTER_ROUTE_NEWS = 'news' -export const ROUTER_ROUTE_ORGANISATIONS = 'organisations' +export const ROUTER_ROUTE_ORGANIZATIONS = 'organisations' diff --git a/apps/datahub/src/app/router/datahub-router.service.ts b/apps/datahub/src/app/router/datahub-router.service.ts index 0f6c7d58a0..c8075389c6 100644 --- a/apps/datahub/src/app/router/datahub-router.service.ts +++ b/apps/datahub/src/app/router/datahub-router.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core' import { Router, Routes } from '@angular/router' import { - ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, + ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, } from '@geonetwork-ui/feature/router' import { HomePageComponent } from '../home/home-page/home-page.component' @@ -12,7 +13,7 @@ import { RecordPageComponent } from '../record/record-page/record-page.component import { ROUTER_ROUTE_HOME, ROUTER_ROUTE_NEWS, - ROUTER_ROUTE_ORGANISATIONS, + ROUTER_ROUTE_ORGANIZATIONS, } from './constants' import { OrganizationPageComponent } from '../organization/organization-page/organization-page.component' @@ -60,12 +61,12 @@ export class DatahubRouterService { }, }, { - path: ROUTER_ROUTE_ORGANISATIONS, + path: ROUTER_ROUTE_ORGANIZATIONS, component: OrganisationsPageComponent, data: { shouldDetach: true, }, - } + }, ], }, { diff --git a/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts b/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts index db5a54dfa1..de250da110 100644 --- a/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts +++ b/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts @@ -40,7 +40,7 @@ export class Gn4PlatformMapper { const { enabled, emailAddresses, - organisation, + organization, kind, lastLoginDate, accountNonExpired, diff --git a/libs/common/domain/src/lib/model/user/user.model.ts b/libs/common/domain/src/lib/model/user/user.model.ts index 591fc7bf18..fefd657049 100644 --- a/libs/common/domain/src/lib/model/user/user.model.ts +++ b/libs/common/domain/src/lib/model/user/user.model.ts @@ -5,6 +5,6 @@ export interface UserModel { name: string surname: string email: string - organisation: string + organization: string profileIcon?: string } diff --git a/libs/data-access/gn4/src/openapi/model/user.api.model.ts b/libs/data-access/gn4/src/openapi/model/user.api.model.ts index 1404ec9743..b9137375be 100644 --- a/libs/data-access/gn4/src/openapi/model/user.api.model.ts +++ b/libs/data-access/gn4/src/openapi/model/user.api.model.ts @@ -21,7 +21,7 @@ export interface UserApiModel { emailAddresses?: Set addresses?: Set primaryAddress?: AddressApiModel - organisation?: string + organization?: string kind?: string lastLoginDate?: string authorities?: Array diff --git a/libs/feature/catalog/src/lib/my-org/my-org.service.spec.ts b/libs/feature/catalog/src/lib/my-org/my-org.service.spec.ts index d2c2624682..efff9f9afa 100644 --- a/libs/feature/catalog/src/lib/my-org/my-org.service.spec.ts +++ b/libs/feature/catalog/src/lib/my-org/my-org.service.spec.ts @@ -69,7 +69,7 @@ describe('MyOrgService', () => { it('should update myOrgDataSubject when authService user$ emits a user', () => { const user: UserModel = { - organisation: 'Géo2France', + organization: 'Géo2France', id: '2', profile: 'profile', username: 'username', @@ -104,8 +104,8 @@ describe('MyOrgService', () => { it('should update myOrgDataSubject when authService allUsers$ emits users', () => { const users: UserApiModel[] = [ - { organisation: 'Géo2France' }, - { organisation: 'Géo2France' }, + { organization: 'Géo2France' }, + { organization: 'Géo2France' }, ] allUsersSubject.next(users) diff --git a/libs/feature/catalog/src/lib/my-org/my-org.service.ts b/libs/feature/catalog/src/lib/my-org/my-org.service.ts index 8840cc3bba..324884f5a7 100644 --- a/libs/feature/catalog/src/lib/my-org/my-org.service.ts +++ b/libs/feature/catalog/src/lib/my-org/my-org.service.ts @@ -27,12 +27,12 @@ export class MyOrgService { this.orgService.organisations$, ]).pipe( map(([user, allUsers, orgs]) => { - const orgName = user.organisation + const orgName = user.organization const org = orgs.find((org) => org.name === orgName) const logoUrl = org?.logoUrl?.toString() const recordCount = org?.recordCount const userList = allUsers.filter( - (user) => user.organisation === orgName + (user) => user.organization === orgName ) const userCount = userList.length return { diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.html b/libs/feature/catalog/src/lib/organisations/organisations.component.html index f832e2289e..3d253d16f3 100644 --- a/libs/feature/catalog/src/lib/organisations/organisations.component.html +++ b/libs/feature/catalog/src/lib/organisations/organisations.component.html @@ -19,7 +19,7 @@ [showContent]="!!organisation.name" > diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html index dedd016072..da523e26c3 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html @@ -1,14 +1,14 @@
@@ -18,20 +18,20 @@ class="shrink-0 mb-3 mt-5 font-title text-21 text-title group-hover:text-primary line-clamp-2 sm:mt-2 transition-colors" data-cy="organizationName" > - {{ organisation.name }}

- {{ organisation.description }} + {{ organization.description }}

folder_open {{ - organisation.recordCount + organization.recordCount }} record.metadata.publications
diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.spec.ts b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.spec.ts index c8b81ce2c7..36a5ef528f 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.spec.ts +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.spec.ts @@ -35,7 +35,7 @@ describe('OrganisationPreviewComponent', () => { fixture = TestBed.createComponent(OrganisationPreviewComponent) component = fixture.componentInstance - component.organisation = organisationMock + component.organization = organisationMock fixture.detectChanges() }) diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.stories.ts b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.stories.ts index d0fbb769c7..2510384d2c 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.stories.ts +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.stories.ts @@ -46,7 +46,7 @@ export default { export const Primary: StoryObj = { args: { - organisation: { + organization: { name: 'Agglo du Saint Quentinois', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts index b9819e4323..d44c8e3d94 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts @@ -14,12 +14,12 @@ import { Organization } from '@geonetwork-ui/common/domain/model/record' changeDetection: ChangeDetectionStrategy.OnPush, }) export class OrganisationPreviewComponent { - @Input() organisation: Organization + @Input() organization: Organization @Input() organisationUrl: string @Output() clickedOrganisation = new EventEmitter() clickOrganisation(event: Event) { event.preventDefault() - this.clickedOrganisation.emit(this.organisation) + this.clickedOrganisation.emit(this.organization) } } From 35981fdc73794a4b511cf075ab7222cb8149dc0c Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:20:20 +0200 Subject: [PATCH 04/10] refactor(datahub): fix some translations --- 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 | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/translations/de.json b/translations/de.json index 822d224de6..7f324b5ebe 100644 --- a/translations/de.json +++ b/translations/de.json @@ -277,7 +277,7 @@ "record.metadata.preview": "Vorschau", "record.metadata.producer": "Datenproduzent", "record.metadata.publication": "Veröffentlichungsdatum", - "record.metadata.publications": "Veröffentlichungen", + "record.metadata.publications": "{count, plural, =0{Veröffentlichungsdatum} one{Veröffentlichungsdatum} other{Veröffentlichungen}}", "record.metadata.quality": "Metadatenqualität", "record.metadata.quality.contact.failed": "Kontakt nicht angegeben", "record.metadata.quality.contact.success": "Kontakt angegeben", diff --git a/translations/en.json b/translations/en.json index c3ed62929c..6e53a4adcd 100644 --- a/translations/en.json +++ b/translations/en.json @@ -280,7 +280,7 @@ "record.metadata.preview": "Preview", "record.metadata.producer": "Data producer", "record.metadata.publication": "Date of publication", - "record.metadata.publications": "publications", + "record.metadata.publications": "{count, plural, =0{publication} one{publication} other{publications}}", "record.metadata.quality": "Metadata Quality", "record.metadata.quality.contact.failed": "Contact is not specified", "record.metadata.quality.contact.success": "Contact is specified", diff --git a/translations/es.json b/translations/es.json index 8fb135e73c..5d45592cfe 100644 --- a/translations/es.json +++ b/translations/es.json @@ -280,7 +280,7 @@ "record.metadata.preview": "", "record.metadata.producer": "", "record.metadata.publication": "", - "record.metadata.publications": "", + "record.metadata.publications": "{count, plural, =0{} one{} other{}}", "record.metadata.quality": "", "record.metadata.quality.contact.failed": "", "record.metadata.quality.contact.success": "", diff --git a/translations/fr.json b/translations/fr.json index eab44644cb..e24c85b469 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -280,7 +280,7 @@ "record.metadata.preview": "Aperçu", "record.metadata.producer": "Producteur de la donnée", "record.metadata.publication": "Date de publication", - "record.metadata.publications": "données", + "record.metadata.publications": "{count, plural, =0{donnée} one{donnée} other{données}}", "record.metadata.quality": "Qualité des métadonnées", "record.metadata.quality.contact.failed": "Contact n'est pas renseigné", "record.metadata.quality.contact.success": "Contact est renseigné", diff --git a/translations/it.json b/translations/it.json index b579543d57..73df9953a8 100644 --- a/translations/it.json +++ b/translations/it.json @@ -280,7 +280,7 @@ "record.metadata.preview": "Anteprima", "record.metadata.producer": "Produttore dei dati", "record.metadata.publication": "Data di pubblicazione", - "record.metadata.publications": "pubblicazioni", + "record.metadata.publications": "{count, plural, =0{pubblicazione} one{pubblicazione} other{pubblicazioni}}", "record.metadata.quality": "Qualità dei metadati", "record.metadata.quality.contact.failed": "Il contatto non è specificato", "record.metadata.quality.contact.success": "Il contatto è specificato", diff --git a/translations/nl.json b/translations/nl.json index 699aef495b..22a7066ba5 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -280,7 +280,7 @@ "record.metadata.preview": "", "record.metadata.producer": "", "record.metadata.publication": "", - "record.metadata.publications": "", + "record.metadata.publications": "{count, plural, =0{} one{} other{}}", "record.metadata.quality": "", "record.metadata.quality.contact.failed": "", "record.metadata.quality.contact.success": "", diff --git a/translations/pt.json b/translations/pt.json index f405a198e1..59b06d0520 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -280,7 +280,7 @@ "record.metadata.preview": "", "record.metadata.producer": "", "record.metadata.publication": "", - "record.metadata.publications": "", + "record.metadata.publications": "{count, plural, =0{} one{} other{}}", "record.metadata.quality": "", "record.metadata.quality.contact.failed": "", "record.metadata.quality.contact.success": "", diff --git a/translations/sk.json b/translations/sk.json index 8bb1864411..90d39e566d 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -279,8 +279,8 @@ "record.metadata.owner": "Katalóg pôvodu", "record.metadata.preview": "Náhľad", "record.metadata.producer": "", - "record.metadata.publication": "", - "record.metadata.publications": "publikácie", + "record.metadata.publication": "publikácia", + "record.metadata.publications": "{count, plural, =0{publikácia} one{publikácia} other{publikácie}}", "record.metadata.quality": "Kvalita metadát", "record.metadata.quality.contact.failed": "Kontakt nie je uvedený", "record.metadata.quality.contact.success": "Kontakt je uvedený", From e5fefcf15015defc6b6cf940cdbbdcdc4205d854 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:25:30 +0200 Subject: [PATCH 05/10] refactor/fix(datahub): refactor and fix key-figure-component --- .../key-figures/key-figures.component.html | 4 ++-- .../dataviz/src/lib/figure/figure.component.html | 14 +++++++++++--- .../src/lib/figure/figure.component.spec.ts | 10 ++++++++-- libs/ui/dataviz/src/lib/figure/figure.component.ts | 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 +- 12 files changed, 33 insertions(+), 21 deletions(-) diff --git a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html index 3bc2976030..aaf2ae7e23 100644 --- a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html +++ b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html @@ -4,7 +4,7 @@ class="py-[37px] pl-[47px] rounded-lg border bg-white mb-5 card-shadow cursor-pointer" [figure]="recordsCount$ | async" [icon]="'folder_open'" - title="catalog.figures.datasets" + [title]="'catalog.figures.datasets'" [color]="'secondary'" >
@@ -13,7 +13,7 @@ class="py-[37px] pl-[47px] rounded-lg bg-white border card-shadow cursor-pointer" [figure]="orgsCount$ | async" [icon]="'corporate_fare'" - title="catalog.figures.organisations" + [title]="'catalog.figures.organizations'" [color]="'secondary'" > diff --git a/libs/ui/dataviz/src/lib/figure/figure.component.html b/libs/ui/dataviz/src/lib/figure/figure.component.html index 7b06c7b37c..2324b2d5c1 100644 --- a/libs/ui/dataviz/src/lib/figure/figure.component.html +++ b/libs/ui/dataviz/src/lib/figure/figure.component.html @@ -1,7 +1,13 @@
- {{ figure }} + {{ + figure + }} {{ unit }}
diff --git a/libs/ui/dataviz/src/lib/figure/figure.component.spec.ts b/libs/ui/dataviz/src/lib/figure/figure.component.spec.ts index b587252eb9..e5029bb01e 100644 --- a/libs/ui/dataviz/src/lib/figure/figure.component.spec.ts +++ b/libs/ui/dataviz/src/lib/figure/figure.component.spec.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { FigureComponent } from './figure.component' +import { TranslateModule } from '@ngx-translate/core' describe('FigureComponent', () => { let component: FigureComponent @@ -11,6 +12,7 @@ describe('FigureComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [FigureComponent], + imports: [TranslateModule.forRoot({})], schemas: [NO_ERRORS_SCHEMA], }) .overrideComponent(FigureComponent, { @@ -66,7 +68,9 @@ describe('FigureComponent', () => { ) }) it('has a tooltip containing the information', () => { - const title = component.hoverTitle + const title = compiled.querySelector( + '[data-test="figureTitle"]' + )?.textContent expect(title).toContain(component.title) expect(title).toContain(component.unit) expect(title).toContain(component.figure) @@ -77,7 +81,9 @@ describe('FigureComponent', () => { component.unit = undefined }) it('does not have undefined in the tooltip', () => { - const title = component.hoverTitle + const title = compiled.querySelector( + '[data-test="figureTitle"]' + )?.textContent expect(title).toContain(component.title) expect(title).toContain(component.figure) expect(title).not.toContain('undefined') diff --git a/libs/ui/dataviz/src/lib/figure/figure.component.ts b/libs/ui/dataviz/src/lib/figure/figure.component.ts index 196240e313..46c4601d4a 100644 --- a/libs/ui/dataviz/src/lib/figure/figure.component.ts +++ b/libs/ui/dataviz/src/lib/figure/figure.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input } from '@angular/core' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' @Component({ selector: 'gn-ui-figure', @@ -10,17 +10,13 @@ export class FigureComponent { @Input() icon!: string @Input() title!: string @Input() figure!: string | number - @Input() unit?: string + @Input() unit = '' @Input() color: 'primary' | 'secondary' = 'primary' - get hoverTitle() { - return `${this.figure.toString()} ${this.unit || ''} -${this.title}` - } - get textClass() { return this.color === 'primary' ? 'text-primary' : 'text-secondary' } + get bgClass() { return this.color === 'primary' ? 'bg-primary-white' : 'bg-secondary-white' } diff --git a/translations/de.json b/translations/de.json index 7f324b5ebe..c7763d473d 100644 --- a/translations/de.json +++ b/translations/de.json @@ -1,7 +1,7 @@ { "button.login": "", "catalog.figures.datasets": "{count, plural, =0{Datensätze} one{Datensatz} other{Datensätze}}", - "catalog.figures.organisations": "{count, plural, =0{Organisationen} one{Organisation} other{Organisationen}}", + "catalog.figures.organizations": "{count, plural, =0{Organisationen} one{Organisation} other{Organisationen}}", "chart.aggregation.average": "Durchschnitt", "chart.aggregation.count": "Anzahl", "chart.aggregation.max": "Maximum", diff --git a/translations/en.json b/translations/en.json index 6e53a4adcd..6f6cf41ad6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,7 +1,7 @@ { "button.login": "Log in", "catalog.figures.datasets": "{count, plural, =0{datasets} one{dataset} other{datasets}}", - "catalog.figures.organisations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", + "catalog.figures.organizations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", "chart.aggregation.average": "average", "chart.aggregation.count": "count", "chart.aggregation.max": "max", diff --git a/translations/es.json b/translations/es.json index 5d45592cfe..721b4a9a65 100644 --- a/translations/es.json +++ b/translations/es.json @@ -1,7 +1,7 @@ { "button.login": "", "catalog.figures.datasets": "conjuntos de datos", - "catalog.figures.organisations": "organizaciones", + "catalog.figures.organizations": "organizaciones", "chart.aggregation.average": "promedio", "chart.aggregation.count": "conteo", "chart.aggregation.max": "máximo", diff --git a/translations/fr.json b/translations/fr.json index e24c85b469..52bd6497b6 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -1,7 +1,7 @@ { "button.login": "Se connecter", "catalog.figures.datasets": "{count, plural, =0{données} one{donnée} other{données}}", - "catalog.figures.organisations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", + "catalog.figures.organizations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", "chart.aggregation.average": "moyenne", "chart.aggregation.count": "nombre", "chart.aggregation.max": "maximum", diff --git a/translations/it.json b/translations/it.json index 73df9953a8..7bd5cf8c50 100644 --- a/translations/it.json +++ b/translations/it.json @@ -2,6 +2,8 @@ "button.login": "", "catalog.figures.datasets": "{count, plural, =0{datasets} one{dataset} other{datasets}}", "catalog.figures.organisations": "{count, plural, =0{organizzazioni} one{organizzazione} other{organizzazioni}}", + "catalog.figures.datasets": "{count, plural, =0{dataset} one{dataset} other{datasets}}", + "catalog.figures.organizations": "{count, plural, =0{organizzazioni} one{organizzazione} other{organizzazioni}}", "chart.aggregation.average": "media", "chart.aggregation.count": "conteggio", "chart.aggregation.max": "massimo", diff --git a/translations/nl.json b/translations/nl.json index 22a7066ba5..bb7a69521b 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -1,7 +1,7 @@ { "button.login": "", "catalog.figures.datasets": "datasets", - "catalog.figures.organisations": "organisaties", + "catalog.figures.organizations": "organisaties", "chart.aggregation.average": "gemiddelde", "chart.aggregation.count": "aantal", "chart.aggregation.max": "max", diff --git a/translations/pt.json b/translations/pt.json index 59b06d0520..a27def7939 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -1,7 +1,7 @@ { "button.login": "", "catalog.figures.datasets": "conjuntos de dados", - "catalog.figures.organisations": "organizações", + "catalog.figures.organizations": "organizações", "chart.aggregation.average": "média", "chart.aggregation.count": "contagem", "chart.aggregation.max": "máximo", diff --git a/translations/sk.json b/translations/sk.json index 90d39e566d..638b411300 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -1,7 +1,7 @@ { "button.login": "", "catalog.figures.datasets": "{count, plural, =0{datasety} one{dataset} other{datasety}}", - "catalog.figures.organisations": "{count, plural, =0{organizácie} one{organizácia} other{organizácie}}", + "catalog.figures.organizations": "{count, plural, =0{organizácie} one{organizácia} other{organizácie}}", "chart.aggregation.average": "priemer", "chart.aggregation.count": "počet", "chart.aggregation.max": "maximum", From 0e9f727a1f9f336d5102e464fbd60c1f50965e2f Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:29:40 +0200 Subject: [PATCH 06/10] feat(datahub): finish organization-page --- apps/datahub/src/app/app.module.ts | 10 +- .../navigation-menu.component.spec.ts | 2 +- .../navigation-menu.component.ts | 4 +- .../organisations-page.component.spec.ts | 59 +- .../organisations-page.component.ts | 14 +- .../header-organization.component.spec.ts | 70 -- ....css => organization-header.component.css} | 0 ...tml => organization-header.component.html} | 20 +- .../organization-header.component.spec.ts | 70 ++ ...nt.ts => organization-header.component.ts} | 22 +- .../organization-details.component.html | 187 ++-- .../organization-details.component.spec.ts | 805 +++++------------- .../organization-details.component.ts | 156 +++- .../organization-page.component.html | 10 +- .../organization-page.component.spec.ts | 86 +- .../organization-page.component.ts | 20 +- .../app/router/datahub-router.service.spec.ts | 9 + apps/datahub/src/styles.css | 3 - apps/metadata-editor/src/app/app.module.ts | 1 + .../fixtures/src/lib/organisations.fixture.ts | 28 + .../src/lib/error/error.component.html | 10 + .../elements/src/lib/error/error.component.ts | 1 + translations/de.json | 6 + translations/en.json | 3 + translations/es.json | 3 + translations/fr.json | 3 + translations/it.json | 5 +- translations/nl.json | 3 + translations/pt.json | 3 + translations/sk.json | 3 + 30 files changed, 773 insertions(+), 843 deletions(-) delete mode 100644 apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts rename apps/datahub/src/app/organization/header-organization/{header-organization.component.css => organization-header.component.css} (100%) rename apps/datahub/src/app/organization/header-organization/{header-organization.component.html => organization-header.component.html} (74%) create mode 100644 apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts rename apps/datahub/src/app/organization/header-organization/{header-organization.component.ts => organization-header.component.ts} (59%) diff --git a/apps/datahub/src/app/app.module.ts b/apps/datahub/src/app/app.module.ts index e8cc4b54f5..534338f09d 100644 --- a/apps/datahub/src/app/app.module.ts +++ b/apps/datahub/src/app/app.module.ts @@ -15,7 +15,8 @@ import { import { DefaultRouterModule, ROUTE_PARAMS, - ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, + ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, RouterService, } from '@geonetwork-ui/feature/router' @@ -147,7 +148,7 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] searchStateId: 'mainSearch', searchRouteComponent: SearchPageComponent, recordRouteComponent: RecordPageComponent, - organizationRouteComponent: OrganizationPageComponent + organizationRouteComponent: OrganizationPageComponent, }), FeatureRecordModule, FeatureCatalogModule, @@ -220,7 +221,10 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] }, }, { provide: RECORD_URL_TOKEN, useValue: `${ROUTER_ROUTE_DATASET}/\${uuid}` }, - { provide: ORGANIZATION_PAGE_URL_TOKEN, useValue: `${ROUTER_ROUTE_ORGANIZATION}/\${name}` }, + { + provide: ORGANIZATION_PAGE_URL_TOKEN, + useValue: `${ROUTER_ROUTE_ORGANIZATION}/\${name}`, + }, { provide: ORGANIZATION_URL_TOKEN, useValue: `${ROUTER_ROUTE_SEARCH}?${ROUTE_PARAMS.PUBLISHER}=\${name}`, diff --git a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts index 29e3bafb81..20e073e7ff 100644 --- a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts +++ b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.spec.ts @@ -1,8 +1,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { - RouterFacade, ROUTER_ROUTE_SEARCH, + RouterFacade, } from '@geonetwork-ui/feature/router' import { TranslateModule } from '@ngx-translate/core' import { readFirst } from '@nx/angular/testing' diff --git a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts index 3aa35c9e18..862c2744cc 100644 --- a/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts +++ b/apps/datahub/src/app/home/navigation-menu/navigation-menu.component.ts @@ -1,8 +1,8 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { marker } from '@biesbjerg/ngx-translate-extract-marker' import { - RouterFacade, ROUTER_ROUTE_SEARCH, + RouterFacade, } from '@geonetwork-ui/feature/router' import { map } from 'rxjs/operators' import { diff --git a/apps/datahub/src/app/home/organisations-page/organisations-page.component.spec.ts b/apps/datahub/src/app/home/organisations-page/organisations-page.component.spec.ts index e719f1e097..67be0fccc8 100644 --- a/apps/datahub/src/app/home/organisations-page/organisations-page.component.spec.ts +++ b/apps/datahub/src/app/home/organisations-page/organisations-page.component.spec.ts @@ -2,27 +2,19 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { OrganisationsPageComponent } from './organisations-page.component' -import { SearchService } from '@geonetwork-ui/feature/search' -import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' -import { of } from 'rxjs' +import { RouterFacade } from '@geonetwork-ui/feature/router' +import { ORGANISATIONS_FIXTURE } from '@geonetwork-ui/common/fixtures' -class SearchServiceMock { - setFilters = jest.fn() -} - -class OrganisationsServiceMock { - getFiltersForOrgs = jest.fn((orgs) => - of({ - orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), - }) - ) +class RouterFacadeMock { + goToOrganization = jest.fn() } describe('OrganisationsPageComponent', () => { let component: OrganisationsPageComponent let fixture: ComponentFixture - let searchService: SearchService - let orgsService: OrganizationsServiceInterface + let routerFacade: RouterFacade + + const selectedOrganization = ORGANISATIONS_FIXTURE[0] beforeEach(async () => { await TestBed.configureTestingModule({ @@ -30,18 +22,13 @@ describe('OrganisationsPageComponent', () => { schemas: [NO_ERRORS_SCHEMA], providers: [ { - provide: SearchService, - useClass: SearchServiceMock, - }, - { - provide: OrganizationsServiceInterface, - useClass: OrganisationsServiceMock, + provide: RouterFacade, + useClass: RouterFacadeMock, }, ], }).compileComponents() - searchService = TestBed.inject(SearchService) - orgsService = TestBed.inject(OrganizationsServiceInterface) + routerFacade = TestBed.inject(RouterFacade) fixture = TestBed.createComponent(OrganisationsPageComponent) component = fixture.componentInstance @@ -52,23 +39,15 @@ describe('OrganisationsPageComponent', () => { expect(component).toBeTruthy() }) - describe('#searchByOrganisation', () => { - beforeEach(() => { - component.searchByOrganisation({ - name: 'MyOrg', - }) - }) - it('generates filters for the org', () => { - expect(orgsService.getFiltersForOrgs).toHaveBeenCalledWith([ - { name: 'MyOrg' }, - ]) - }) - it('updates filters to filter on the org', () => { - expect(searchService.setFilters).toHaveBeenCalledWith({ - orgs: { - MyOrg: true, - }, - }) + describe('onOrganizationSelection', () => { + it('should goToOrganization page', () => { + component.onOrganizationSelection(selectedOrganization) + + fixture.detectChanges() + + expect(routerFacade.goToOrganization).toHaveBeenCalledWith( + selectedOrganization.name + ) }) }) }) diff --git a/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts b/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts index 37826aee72..23f0991d11 100644 --- a/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts +++ b/apps/datahub/src/app/home/organisations-page/organisations-page.component.ts @@ -9,19 +9,9 @@ import { RouterFacade } from '@geonetwork-ui/feature/router' changeDetection: ChangeDetectionStrategy.OnPush, }) export class OrganisationsPageComponent { - constructor( - // private searchService: SearchService, - // private orgsService: OrganizationsServiceInterface, - private searchRouter: RouterFacade - ) {} - - // searchByOrganisation(organisation: Organization) { - // this.orgsService - // .getFiltersForOrgs([organisation]) - // .subscribe((filters) => this.searchService.setFilters(filters)) - // } + constructor(private routerFacade: RouterFacade) {} onOrganizationSelection(organisation: Organization) { - this.searchRouter.goToOrganization(organisation.name) + this.routerFacade.goToOrganization(organisation.name) } } diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts b/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts deleted file mode 100644 index 3ea9850ee6..0000000000 --- a/apps/datahub/src/app/organization/header-organization/header-organization.component.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core' -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' -import { MdViewFacade } from '@geonetwork-ui/feature/record' -import { SearchService } from '@geonetwork-ui/feature/search' -import { TranslateModule } from '@ngx-translate/core' -import { BehaviorSubject } from 'rxjs' - -import { HeaderOrganizationComponent } from './header-organization.component' - -jest.mock('@geonetwork-ui/util/app-config', () => ({ - getThemeConfig: () => ({ - HEADER_BACKGROUND: 'red', - HEADER_FOREGROUND_COLOR: 'white', - }), - getGlobalConfig() { - return { - LANGUAGES: ['en', 'es'], - } - }, -})) - -const searchServiceMock = { - updateFilters: jest.fn(), -} - -class MdViewFacadeMock { - mapApiLinks$ = new BehaviorSubject([]) - geoDataLinks$ = new BehaviorSubject([]) -} - -describe('HeaderRecordComponent', () => { - let component: HeaderOrganizationComponent - let fixture: ComponentFixture - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [HeaderOrganizationComponent], - imports: [TranslateModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], - providers: [ - { provide: SearchService, useValue: searchServiceMock }, - { - provide: MdViewFacade, - useClass: MdViewFacadeMock, - }, - ], - }).compileComponents() - }) - - beforeEach(() => { - fixture = TestBed.createComponent(HeaderOrganizationComponent) - component = fixture.componentInstance - component.metadata = { - ...DATASET_RECORDS[0], - } - fixture.detectChanges() - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) - - describe('#back', () => { - it('searchFilter updateSearch', () => { - component.back() - expect(searchServiceMock.updateFilters).toHaveBeenCalledWith({}) - }) - }) -}) diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.css b/apps/datahub/src/app/organization/header-organization/organization-header.component.css similarity index 100% rename from apps/datahub/src/app/organization/header-organization/header-organization.component.css rename to apps/datahub/src/app/organization/header-organization/organization-header.component.css diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.html b/apps/datahub/src/app/organization/header-organization/organization-header.component.html similarity index 74% rename from apps/datahub/src/app/organization/header-organization/header-organization.component.html rename to apps/datahub/src/app/organization/header-organization/organization-header.component.html index 8eb9d0b43e..9c16ff9bc0 100644 --- a/apps/datahub/src/app/organization/header-organization/header-organization.component.html +++ b/apps/datahub/src/app/organization/header-organization/organization-header.component.html @@ -1,6 +1,6 @@
@@ -22,7 +22,6 @@
@@ -36,16 +35,19 @@

{{ organization.recordCount }}

-

+

organization.header.recordCount

-

- • -

- {{ organization.website.href }} open_in_new +

+ + {{ organization.website.href }} + open_in_new
diff --git a/apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts b/apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts new file mode 100644 index 0000000000..2ca8215648 --- /dev/null +++ b/apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts @@ -0,0 +1,70 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { OrganizationHeaderComponent } from './organization-header.component' +import { UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { TranslateModule } from '@ngx-translate/core' +import { UiCatalogModule } from '@geonetwork-ui/ui/catalog' +import { AsyncPipe, Location, NgIf } from '@angular/common' +import { MatIconModule } from '@angular/material/icon' +import { ORGANISATIONS_FIXTURE } from '@geonetwork-ui/common/fixtures' + +jest.mock('@geonetwork-ui/util/app-config', () => ({ + getThemeConfig: () => ({ + HEADER_BACKGROUND: 'red', + HEADER_FOREGROUND_COLOR: 'white', + }), + getGlobalConfig() { + return { + LANGUAGES: ['en', 'es'], + } + }, +})) + +const locationMock: Partial = { + back: jest.fn(), +} + +describe('OrganizationHeaderComponent', () => { + let component: OrganizationHeaderComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + OrganizationHeaderComponent, + UiInputsModule, + TranslateModule, + UiCatalogModule, + NgIf, + MatIconModule, + AsyncPipe, + TranslateModule.forRoot(), + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: Location, + useValue: locationMock, + }, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(OrganizationHeaderComponent) + component = fixture.componentInstance + component.organization = ORGANISATIONS_FIXTURE[0] + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('#back', () => { + it('calls the back function of Location', () => { + component.back() + expect(locationMock.back).toHaveBeenCalled() + }) + }) +}) diff --git a/apps/datahub/src/app/organization/header-organization/header-organization.component.ts b/apps/datahub/src/app/organization/header-organization/organization-header.component.ts similarity index 59% rename from apps/datahub/src/app/organization/header-organization/header-organization.component.ts rename to apps/datahub/src/app/organization/header-organization/organization-header.component.ts index 2680f51c9c..58c51e903f 100644 --- a/apps/datahub/src/app/organization/header-organization/header-organization.component.ts +++ b/apps/datahub/src/app/organization/header-organization/organization-header.component.ts @@ -1,18 +1,16 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { SearchService } from '@geonetwork-ui/feature/search' import { getGlobalConfig, getThemeConfig } from '@geonetwork-ui/util/app-config' -import { MdViewFacade } from '@geonetwork-ui/feature/record' -import { TranslateModule, TranslateService } from '@ngx-translate/core' +import { TranslateModule } from '@ngx-translate/core' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { UiCatalogModule } from '@geonetwork-ui/ui/catalog' import { Organization } from '@geonetwork-ui/common/domain/model/record' -import { AsyncPipe, NgIf } from '@angular/common' +import { AsyncPipe, Location, NgIf } from '@angular/common' import { MatIconModule } from '@angular/material/icon' @Component({ - selector: 'datahub-header-organization', - templateUrl: './header-organization.component.html', - styleUrls: ['./header-organization.component.css'], + selector: 'datahub-organization-header', + templateUrl: './organization-header.component.html', + styleUrls: ['./organization-header.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ @@ -24,7 +22,7 @@ import { MatIconModule } from '@angular/material/icon' AsyncPipe, ], }) -export class HeaderOrganizationComponent { +export class OrganizationHeaderComponent { @Input() organization: Organization backgroundCss = @@ -33,13 +31,9 @@ export class HeaderOrganizationComponent { foregroundColor = getThemeConfig().HEADER_FOREGROUND_COLOR || '#ffffff' showLanguageSwitcher = getGlobalConfig().LANGUAGES?.length > 0 - constructor( - private searchService: SearchService, - public facade: MdViewFacade, - private translateService: TranslateService - ) {} + constructor(private location: Location) {} back() { - this.searchService.updateFilters({}) + this.location.back() } } diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.html b/apps/datahub/src/app/organization/organization-details/organization-details.component.html index faa1b3d979..9ef5da0aef 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.html +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.html @@ -1,61 +1,144 @@ -
-
-
-

- {{ organization.description }} -

-
-
- -
+ diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts index d1ca754499..6e4d6078ac 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts @@ -1,60 +1,45 @@ import { - Component, - EventEmitter, - Input, + ChangeDetectionStrategy, + ChangeDetectorRef, + DebugElement, NO_ERRORS_SCHEMA, - Output, } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' -import { By } from '@angular/platform-browser' -import { SourcesService } from '@geonetwork-ui/feature/catalog' -import { MapManagerService } from '@geonetwork-ui/feature/map' -import { SearchService } from '@geonetwork-ui/feature/search' -import { - ErrorType, - SearchResultsErrorComponent, -} from '@geonetwork-ui/ui/elements' +import { SearchFacade } from '@geonetwork-ui/feature/search' import { TranslateModule } from '@ngx-translate/core' import { BehaviorSubject, of } from 'rxjs' import { OrganizationDetailsComponent } from './organization-details.component' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' -import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' -import { MdViewFacade } from '@geonetwork-ui/feature/record' import { - CatalogRecord, - DatasetRecord, - DatasetServiceDistribution, - Individual, - Keyword, - Organization, -} from '@geonetwork-ui/common/domain/model/record' - -const SAMPLE_RECORD = { - ...DATASET_RECORDS[0], - extras: { - catalogUuid: 'catalog-0001', - }, -} + DATASET_RECORDS, + ORGANISATIONS_FIXTURE, +} from '@geonetwork-ui/common/fixtures' +import { AsyncPipe, NgForOf, NgIf } from '@angular/common' +import { + ButtonComponent, + PreviousNextButtonsComponent, +} from '@geonetwork-ui/ui/inputs' +import { MatIconModule } from '@angular/material/icon' +import { + BlockListComponent, + CarouselComponent, + MaxLinesComponent, +} from '@geonetwork-ui/ui/layout' +import { LetDirective } from '@ngrx/component' +import { LinkCardComponent, UiElementsModule } from '@geonetwork-ui/ui/elements' +import { UiSearchModule } from '@geonetwork-ui/ui/search' +import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz' +import { RouterLink } from '@angular/router' +import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' +import { Organization } from '@geonetwork-ui/common/domain/model/record' +import { RouterTestingModule } from '@angular/router/testing' +import { By } from '@angular/platform-browser' +import { ROUTER_ROUTE_SEARCH } from '@geonetwork-ui/feature/router' -class MdViewFacadeMock { - isPresent$ = new BehaviorSubject(false) - metadata$ = new BehaviorSubject(SAMPLE_RECORD) - mapApiLinks$ = new BehaviorSubject([]) - dataLinks$ = new BehaviorSubject([]) - geoDataLinks$ = new BehaviorSubject([]) - downloadLinks$ = new BehaviorSubject([]) - apiLinks$ = new BehaviorSubject([]) - otherLinks$ = new BehaviorSubject([]) - related$ = new BehaviorSubject(null) - error$ = new BehaviorSubject(null) -} +let getHTMLElement: (dataTest: string) => HTMLElement | undefined -class SearchServiceMock { - setFilters = jest.fn() - updateFilters = jest.fn() -} -class SourcesServiceMock { - getSourceLabel = jest.fn(() => of('catalog label')) +const changeDetectorRefMock: Partial = { + markForCheck: jest.fn(), } class OrganisationsServiceMock { @@ -63,160 +48,109 @@ class OrganisationsServiceMock { orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), }) ) + organisations$ = of(ORGANISATIONS_FIXTURE) } -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-map-view', - template: '
', -}) -export class MockDataMapComponent {} - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-data-view', - template: '
', -}) -export class MockDataViewComponent {} - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-data-view-share', - template: '
', -}) -export class MockDataViewShareComponent {} - -@Component({ - selector: 'datahub-record-downloads', - template: '
', -}) -export class MockDataDownloadsComponent {} +const anOrganizationWithManyDatasets: Organization = ORGANISATIONS_FIXTURE[0] +const anOrganizationWithOnlyOneDataset: Organization = { + ...ORGANISATIONS_FIXTURE[0], + recordCount: 1, +} -@Component({ - selector: 'datahub-record-otherlinks', - template: '
', -}) -export class MockDataOtherlinksComponent {} +const oneDataset = [DATASET_RECORDS[0]] +const manyDatasets = DATASET_RECORDS.concat(DATASET_RECORDS[0]) -@Component({ - selector: 'datahub-record-apis', - template: '
', -}) -export class MockDataApisComponent {} +const organizationIsLoading = new BehaviorSubject(false) +const totalPages = new BehaviorSubject(10) +const currentPage = new BehaviorSubject(0) +const results = new BehaviorSubject(manyDatasets) -@Component({ - selector: 'datahub-record-related-records', - template: '
', -}) -export class MockRelatedComponent {} +const desiredPageSize = 3 -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-metadata-info', - template: '
', -}) -export class MockMetadataInfoComponent { - @Input() metadata: Partial - @Input() incomplete: boolean - @Output() keyword = new EventEmitter() -} - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-metadata-contact', - template: '
', -}) -export class MockMetadataContactComponent { - @Input() metadata: Partial - @Output() organizationClick = new EventEmitter() - @Output() contactClick = new EventEmitter() -} - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-metadata-catalog', - template: '
', -}) -export class MockMetadataCatalogComponent { - @Input() sourceLabel: string -} +class SearchFacadeMock { + private pageSize = desiredPageSize -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-record-api-form', - template: '
', -}) -export class MockRecordApiFormComponent { - @Input() apiLink: DatasetServiceDistribution -} -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'gn-ui-image-overlay-preview', - template: '
', -}) -export class MockImgOverlayPreviewComponent { - @Input() imageUrl: string - @Output() isPlaceholderShown = new EventEmitter() + setPageSize = jest.fn((pageSize: number) => (this.pageSize = pageSize)) + setFilters = jest.fn(() => new SearchFacadeMock()) + setSortBy = jest.fn(() => new SearchFacadeMock()) + results$ = results.asObservable() + isLoading$ = organizationIsLoading.asObservable() + totalPages$ = totalPages.asObservable() + isBeginningOfResults$ = of(currentPage.getValue() === 1) + isEndOfResults$ = of(totalPages.getValue() === currentPage.getValue()) + currentPage$ = currentPage.asObservable() + paginate = jest.fn(() => { + currentPage.next(currentPage.getValue() + 1) + return new SearchFacadeMock() + }) } -describe('RecordMetadataComponent', () => { +describe('OrganizationDetailsComponent', () => { let component: OrganizationDetailsComponent let fixture: ComponentFixture - let facade - let searchService: SearchService - let sourcesService: SourcesService + let searchFacade: SearchFacade + let debugElement: DebugElement beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - OrganizationDetailsComponent, - MockDataMapComponent, - MockDataViewComponent, - MockDataViewShareComponent, - MockDataDownloadsComponent, - MockDataOtherlinksComponent, - MockDataApisComponent, - MockRelatedComponent, - SearchResultsErrorComponent, - MockMetadataInfoComponent, - MockMetadataCatalogComponent, - MockMetadataContactComponent, - MockRecordApiFormComponent, - MockImgOverlayPreviewComponent, - ], + declarations: [], schemas: [NO_ERRORS_SCHEMA], - imports: [TranslateModule.forRoot()], + imports: [ + AsyncPipe, + NgIf, + ButtonComponent, + MatIconModule, + TranslateModule, + CarouselComponent, + BlockListComponent, + LetDirective, + LinkCardComponent, + NgForOf, + PreviousNextButtonsComponent, + UiElementsModule, + UiSearchModule, + MaxLinesComponent, + UiDatavizModule, + RouterLink, + UiWidgetsModule, + TranslateModule.forRoot(), + RouterTestingModule, + ], providers: [ { - provide: MdViewFacade, - useClass: MdViewFacadeMock, - }, - { - provide: MapManagerService, - useValue: {}, - }, - { - provide: SearchService, - useClass: SearchServiceMock, + provide: OrganizationsServiceInterface, + useClass: OrganisationsServiceMock, }, { - provide: SourcesService, - useClass: SourcesServiceMock, + provide: SearchFacade, + useClass: SearchFacadeMock, }, { - provide: OrganizationsServiceInterface, - useClass: OrganisationsServiceMock, + provide: ChangeDetectorRef, + useValue: changeDetectorRefMock, }, ], - }).compileComponents() - facade = TestBed.inject(MdViewFacade) - searchService = TestBed.inject(SearchService) - sourcesService = TestBed.inject(SourcesService) - }) + }) + .overrideComponent(OrganizationDetailsComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + }, + }) + .compileComponents() + + searchFacade = TestBed.inject(SearchFacade) - beforeEach(() => { fixture = TestBed.createComponent(OrganizationDetailsComponent) component = fixture.componentInstance + debugElement = fixture.debugElement + + getHTMLElement = (dataTest: string) => { + const debugEl = debugElement.query(By.css(`[data-test="${dataTest}"]`)) + return debugEl ? (debugEl.nativeElement as HTMLElement) : undefined + } + + component.organization = anOrganizationWithManyDatasets + fixture.detectChanges() }) @@ -224,442 +158,163 @@ describe('RecordMetadataComponent', () => { expect(component).toBeTruthy() }) - describe('about', () => { - let metadataInfo: MockMetadataInfoComponent - let metadataContact: MockMetadataContactComponent - let catalogComponent: MockMetadataCatalogComponent - - beforeEach(() => { - facade.isPresent$.next(true) - fixture.detectChanges() - metadataInfo = fixture.debugElement.query( - By.directive(MockMetadataInfoComponent) - ).componentInstance - metadataContact = fixture.debugElement.query( - By.directive(MockMetadataContactComponent) - ).componentInstance - catalogComponent = fixture.debugElement.query( - By.directive(MockMetadataCatalogComponent) - ).componentInstance - }) - describe('if metadata present', () => { - it('shows the full metadata', () => { - expect(metadataInfo.metadata).toHaveProperty('abstract') - }) - it('shows the metadata contact', () => { - expect(metadataContact.metadata).toHaveProperty('contacts') - }) - it('shows the metadata catalog', () => { - expect(sourcesService.getSourceLabel).toBeCalledWith( - SAMPLE_RECORD.extras.catalogUuid + describe('Left column', () => { + describe('Organization description', () => { + it('should contain the organization description', () => { + const organizationDescriptionHtml = getHTMLElement( + 'organizationDescription' + ) + + expect(organizationDescriptionHtml.textContent.trim()).toEqual( + anOrganizationWithManyDatasets.description?.trim() ) - expect(catalogComponent.sourceLabel).toEqual('catalog label') - }) - }) - describe('if metadata not present', () => { - beforeEach(() => { - facade.isPresent$.next(false) - fixture.detectChanges() - metadataInfo = fixture.debugElement.query( - By.directive(MockMetadataInfoComponent) - ).componentInstance - }) - it('shows a placeholder', () => { - expect(metadataInfo.metadata).not.toHaveProperty('abstract') - expect(metadataInfo.incomplete).toBeTruthy() - }) - it('does not display the metadata contact component', () => { - expect( - fixture.debugElement.query(By.directive(MockMetadataContactComponent)) - ).toBeFalsy() - }) - it('does not display the metadata catalog component', () => { - expect( - fixture.debugElement.query(By.directive(MockMetadataCatalogComponent)) - ).toBeFalsy() - }) - it('does not display the image overlay preview', () => { - expect( - fixture.debugElement.query( - By.directive(MockImgOverlayPreviewComponent) - ) - ).toBeFalsy() - }) - }) - describe('Image Overlay Preview', () => { - describe('if metadata without overview', () => { - let imgOverlayPreview: MockImgOverlayPreviewComponent - beforeEach(() => { - facade.isPresent$.next(true) - facade.metadata$.next({}) - fixture.detectChanges() - imgOverlayPreview = fixture.debugElement.query( - By.directive(MockImgOverlayPreviewComponent) - ).componentInstance - }) - it('should send undefined as imageUrl to imgOverlayPreview component', () => { - expect(imgOverlayPreview).toBeTruthy() - expect(imgOverlayPreview.imageUrl).toBe(undefined) - }) - }) - describe('if metadata with overview', () => { - let imgOverlayPreview: MockImgOverlayPreviewComponent - beforeEach(() => { - facade.isPresent$.next(true) - fixture.detectChanges() - imgOverlayPreview = fixture.debugElement.query( - By.directive(MockImgOverlayPreviewComponent) - ).componentInstance - }) - describe('and url defined', () => { - it('should send the imageUrl to imgOverlayPreview component', () => { - expect(imgOverlayPreview).toBeTruthy() - expect(imgOverlayPreview.imageUrl).toBeDefined() - }) - }) - describe('and url undefined', () => { - beforeEach(() => { - facade.metadata$.next({ overviews: [] }) - fixture.detectChanges() - }) - it('should send the imagUrl as null to imgOverlayPreview component', () => { - expect(imgOverlayPreview).toBeTruthy() - expect(imgOverlayPreview.imageUrl).toBeNull() - }) - }) }) }) }) - describe('Preview', () => { - describe('when no MAPAPI, GEODATA nor DATA link', () => { - beforeEach(() => { - fixture.detectChanges() + describe('Right column', () => { + describe('Organization dataset count', () => { + it('should have the right count of dataset', () => { + const organizationDatasetCount = getHTMLElement( + 'organizationDatasetCount' + ).querySelector('[data-test="figure"]') + + expect(organizationDatasetCount.innerHTML).toEqual( + anOrganizationWithManyDatasets.recordCount?.toString() + ) }) - it('does not render preview content', () => { - expect(fixture.debugElement.query(By.css('#preview'))).toBeFalsy() + }) + + describe('Organization email', () => { + it('should have the email button', () => { + const organizationEmail = getHTMLElement('organizationEmail') + + expect(organizationEmail).toBeTruthy() + + expect(organizationEmail?.getAttribute('href')).toEqual( + `mailto:${anOrganizationWithManyDatasets.email}` + ) }) }) }) - describe('Map view', () => { - let mapTab - let tabGroup - describe('when DATA link, but no MAPAPI and no GEODATA link', () => { - beforeEach(() => { - facade.dataLinks$.next(['link']) + + describe('Last Published datasets', () => { + describe('Previous Next buttons', () => { + it('should not be displayed if organization is loading', () => { + organizationIsLoading.next(true) fixture.detectChanges() - mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] - tabGroup = fixture.debugElement.queryAll(By.css('mat-tab-group'))[0] - }) - it('renders preview, map tab is disabled', () => { - expect(mapTab.nativeNode.disabled).toBe(true) - }) - it('renders preview, table tab is selected', () => { - expect(tabGroup.nativeNode.selectedIndex).toBe(1) - }) - it('does not render map component', () => { + + const organizationDetailsLastPublishedDatasetsPreviousNextButtons = + getHTMLElement( + 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' + ) + expect( - fixture.debugElement.query(By.directive(MockDataMapComponent)) + organizationDetailsLastPublishedDatasetsPreviousNextButtons ).toBeFalsy() }) - }) - describe('when a MAPAPI link present', () => { - beforeEach(() => { - facade.mapApiLinks$.next(['link']) + + it('should not be displayed organization is loaded but has no pagination', () => { + organizationIsLoading.next(false) + totalPages.next(1) fixture.detectChanges() - mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] - }) - it('renders preview, map tab is enabled', () => { - expect(mapTab.nativeNode.disabled).toBe(false) - }) - it('renders map component', () => { + + const organizationDetailsLastPublishedDatasetsPreviousNextButtons = + getHTMLElement( + 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' + ) + expect( - fixture.debugElement.query(By.directive(MockDataMapComponent)) - ).toBeTruthy() + organizationDetailsLastPublishedDatasetsPreviousNextButtons + ).toBeFalsy() }) - }) - describe('when a GEODATA link present', () => { - beforeEach(() => { - facade.geoDataLinks$.next(['link']) + + it('should be displayed if organization is loadded and have pagination', () => { + organizationIsLoading.next(false) + totalPages.next(10) fixture.detectChanges() - mapTab = fixture.debugElement.queryAll(By.css('mat-tab'))[0] - }) - it('renders preview, map tab is enabled', () => { - expect(mapTab.nativeNode.disabled).toBe(false) - }) - it('renders map component', () => { + + const organizationDetailsLastPublishedDatasetsPreviousNextButtons = + getHTMLElement( + 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' + ) + expect( - fixture.debugElement.query(By.directive(MockDataMapComponent)) + organizationDetailsLastPublishedDatasetsPreviousNextButtons ).toBeTruthy() }) - }) - }) - describe('Data view - table and chart', () => { - let tableTab - let chartTab - let tabGroup - describe('when MAPAPI link, but no DATA and no GEODATA link', () => { - beforeEach(() => { - facade.mapApiLinks$.next(['link']) - facade.dataLinks$.next(null) - facade.geoDataLinks$.next(null) + + it('should call paginate from the facade if button is clicked', () => { + const initialPageNumber = currentPage.getValue() + const nextPageNumber = initialPageNumber + 1 + + const organizationDetailsLastPublishedDatasetsPreviousNextButtons = + getHTMLElement( + 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' + ) + + const nextButton = + organizationDetailsLastPublishedDatasetsPreviousNextButtons?.querySelector( + '[data-test="nextButton"]' + ) as HTMLElement + + ;(nextButton?.firstChild as HTMLElement).click() fixture.detectChanges() - tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] - chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] - tabGroup = fixture.debugElement.queryAll(By.css('mat-tab-group'))[0] - }) - it('renders preview, table tab is disabled', () => { - expect(tableTab.nativeNode.disabled).toBe(true) - }) - it('renders preview, chart tab is disabled', () => { - expect(chartTab.nativeNode.disabled).toBe(true) - }) - it('renders preview, map tab is selected', () => { - expect(tabGroup.nativeNode.selectedIndex).toBe(0) - }) - it('does not render any data view component', () => { - expect( - fixture.debugElement.query(By.directive(MockDataViewComponent)) - ).toBeFalsy() - }) - it('does not render the permalink component', () => { - expect( - fixture.debugElement.query(By.directive(MockDataViewShareComponent)) - ).toBeFalsy() - }) - }) - describe('when a DATA link present', () => { - beforeEach(() => { - facade.dataLinks$.next(['link']) + + expect(searchFacade.paginate).toHaveBeenCalledWith(nextPageNumber) + + const previousButton = + organizationDetailsLastPublishedDatasetsPreviousNextButtons?.querySelector( + '[data-test="previousButton"]' + ) as HTMLElement + + ;(previousButton?.firstChild as HTMLElement).click() fixture.detectChanges() - tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] - chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] - }) - it('renders preview, table tab is enabled', () => { - expect(tableTab.nativeNode.disabled).toBe(false) - }) - it('renders preview, chart tab is enabled', () => { - expect(chartTab.nativeNode.disabled).toBe(false) - }) - it('renders two data view components (for table and chart tabs)', () => { - expect( - fixture.debugElement.queryAll(By.directive(MockDataViewComponent)) - .length - ).toEqual(2) - }) - it('does not render the permalink component', () => { - expect( - fixture.debugElement.query(By.directive(MockDataViewShareComponent)) - ).toBeFalsy() + + expect(searchFacade.paginate).toHaveBeenCalledWith(initialPageNumber) }) - describe('when selectedTabIndex$ is 2 (chart tab)', () => { - beforeEach(() => { - component.selectedTabIndex$.next(2) - fixture.detectChanges() - }) - it('renders the permalink component', () => { + + describe('Search all button', () => { + it('should send to the search page filtered on the correct organization', () => { + const organizationDetailsLastPublishedDatasetsSearchAllButton = + getHTMLElement( + 'organizationDetailsLastPublishedDatasetsSearchAllButton' + ) + expect( - fixture.debugElement.query(By.directive(MockDataViewShareComponent)) + organizationDetailsLastPublishedDatasetsSearchAllButton ).toBeTruthy() + + expect( + organizationDetailsLastPublishedDatasetsSearchAllButton?.getAttribute( + 'href' + ) + ).toEqual( + `/${ROUTER_ROUTE_SEARCH}?publisher=${encodeURIComponent( + anOrganizationWithManyDatasets.name + )}` + ) }) }) }) - describe('when a GEODATA link present', () => { - beforeEach(() => { - facade.geoDataLinks$.next(['link']) - fixture.detectChanges() - tableTab = fixture.debugElement.queryAll(By.css('mat-tab'))[1] - chartTab = fixture.debugElement.queryAll(By.css('mat-tab'))[2] - }) - it('renders preview, table tab is enabled', () => { - expect(tableTab.nativeNode.disabled).toBe(false) - }) - it('renders preview, chart tab is enabled', () => { - expect(chartTab.nativeNode.disabled).toBe(false) - }) - it('renders two data view components (for table and chart tabs)', () => { - expect( - fixture.debugElement.queryAll(By.directive(MockDataViewComponent)) - .length - ).toEqual(2) - }) - }) - }) - describe('Downloads', () => { - let downloadsComponent - describe('when no DOWNLOAD link', () => { - beforeEach(() => { - fixture.detectChanges() - downloadsComponent = fixture.debugElement.query( - By.directive(MockDataDownloadsComponent) - ) - }) - it('download component does not render', () => { - expect(downloadsComponent).toBeFalsy() - }) - }) - describe('when DOWNLOAD link', () => { - beforeEach(() => { - facade.downloadLinks$.next(['link']) - fixture.detectChanges() - downloadsComponent = fixture.debugElement.query( - By.directive(MockDataDownloadsComponent) - ) - }) - it('download component renders', () => { - expect(downloadsComponent).toBeTruthy() - }) - }) - }) - describe('Otherlinks', () => { - let otherLinksComponent - describe('when no OTHER link', () => { - beforeEach(() => { - fixture.detectChanges() - otherLinksComponent = fixture.debugElement.query( - By.directive(MockDataOtherlinksComponent) - ) - }) - it('otherlink component does not render', () => { - expect(otherLinksComponent).toBeFalsy() - }) - }) - describe('when OTHER link', () => { - beforeEach(() => { - facade.otherLinks$.next(['link']) - fixture.detectChanges() - otherLinksComponent = fixture.debugElement.query( - By.directive(MockDataOtherlinksComponent) - ) - }) - it('otherlink component renders', () => { - expect(otherLinksComponent).toBeTruthy() - }) - }) - }) - describe('API', () => { - let apiComponent - describe('when no API link', () => { - beforeEach(() => { - fixture.detectChanges() - apiComponent = fixture.debugElement.query( - By.directive(MockDataApisComponent) - ) - }) - it('API component does not render', () => { - expect(apiComponent).toBeFalsy() - }) - }) - describe('when API link', () => { - beforeEach(() => { - facade.apiLinks$.next(['link']) - fixture.detectChanges() - apiComponent = fixture.debugElement.query( - By.directive(MockDataApisComponent) - ) - }) - it('API component renders', () => { - expect(apiComponent).toBeTruthy() - }) - }) - }) - describe('related records', () => { - let relatedComponent - describe('when no related records', () => { - beforeEach(() => { - facade.related$.next([]) - fixture.detectChanges() - relatedComponent = fixture.debugElement.query( - By.directive(MockRelatedComponent) + describe('Last published datasets', () => { + it('should display the datasets properly', () => { + const organizationPageLastPublishedDatasets = getHTMLElement( + 'organizationPageLastPublishedDatasets' ) - }) - it('Related component does not render', () => { - expect(relatedComponent).toBeFalsy() - }) - }) - describe('when related records', () => { - beforeEach(() => { - facade.related$.next([{ title: 'title' }]) - fixture.detectChanges() - relatedComponent = fixture.debugElement.query( - By.directive(MockRelatedComponent) - ) - }) - it('Related component renders', () => { - expect(relatedComponent).toBeTruthy() - }) - }) - }) - - describe('#onInfoKeywordClick', () => { - it('call searchService for any', () => { - component.onInfoKeywordClick({ - thesaurus: { id: 'geonetwork.thesaurus.local' }, - type: 'other', - label: 'international', - }) - expect(searchService.updateFilters).toHaveBeenCalledWith({ - any: 'international', - }) - }) - }) - describe('#onContactClick', () => { - it('call update search for OrgForResource', () => { - component.onOrganizationClick({ - name: 'MyOrganization', - website: new URL('https://www.my.org/info'), - logoUrl: new URL('https://www.my.org/logo.png'), - description: 'A generic organization', - }) - expect(searchService.updateFilters).toHaveBeenCalledWith({ - orgs: { - MyOrganization: true, - }, - }) - }) - }) - describe('error handling', () => { - describe('normal', () => { - it('does not show errors', () => { - const result = fixture.debugElement.query( - By.directive(SearchResultsErrorComponent) - ) - expect(result).toBeFalsy() - }) - }) - describe('record not found', () => { - beforeEach(() => { - facade.error$.next({ notFound: true }) - fixture.detectChanges() - }) - it('shows error', () => { - const result = fixture.debugElement.query( - By.directive(SearchResultsErrorComponent) + expect(organizationPageLastPublishedDatasets).toBeTruthy() + expect(organizationPageLastPublishedDatasets?.children.length).toEqual( + desiredPageSize ) - expect(result).toBeTruthy() - expect(result.componentInstance.type).toBe(ErrorType.RECORD_NOT_FOUND) - expect(result.componentInstance.error).toBe(undefined) - expect(result.componentInstance.recordId).toBe( - SAMPLE_RECORD.uniqueIdentifier - ) - }) - }) - describe('other error', () => { - beforeEach(() => { - facade.error$.next({ otherError: 'This is an Error!' }) + results.next(oneDataset) fixture.detectChanges() - }) - it('shows error', () => { - const result = fixture.debugElement.query( - By.directive(SearchResultsErrorComponent) - ) - expect(result).toBeTruthy() - expect(result.componentInstance.type).toBe(ErrorType.RECEIVED_ERROR) - expect(result.componentInstance.error).toBe('This is an Error!') + expect(organizationPageLastPublishedDatasets?.children.length).toEqual( + 1 + ) }) }) }) diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts index b41d1207c1..23ab3fd7a2 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts @@ -1,15 +1,43 @@ import { + AfterViewInit, ChangeDetectionStrategy, + ChangeDetectorRef, Component, Input, - OnChanges, - SimpleChanges, + OnDestroy, + OnInit, + ViewChild, } from '@angular/core' -import { AsyncPipe, NgIf } from '@angular/common' -import { Organization } from '@geonetwork-ui/common/domain/model/record' -import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { AsyncPipe, NgForOf, NgIf } from '@angular/common' +import { + CatalogRecord, + Organization, +} from '@geonetwork-ui/common/domain/model/record' +import { + ButtonComponent, + PreviousNextButtonsComponent, +} from '@geonetwork-ui/ui/inputs' import { MatIconModule } from '@angular/material/icon' import { TranslateModule } from '@ngx-translate/core' +import { + BlockListComponent, + CarouselComponent, + MaxLinesComponent, +} from '@geonetwork-ui/ui/layout' +import { LetDirective } from '@ngrx/component' +import { + ErrorType, + LinkCardComponent, + UiElementsModule, +} from '@geonetwork-ui/ui/elements' +import { UiSearchModule } from '@geonetwork-ui/ui/search' +import { SearchFacade } from '@geonetwork-ui/feature/search' +import { Observable, of, Subscription, switchMap } from 'rxjs' +import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz' +import { RouterLink } from '@angular/router' +import { ROUTER_ROUTE_SEARCH } from '@geonetwork-ui/feature/router' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' @Component({ selector: 'datahub-organization-details', @@ -17,12 +45,122 @@ import { TranslateModule } from '@ngx-translate/core' styleUrls: ['./organization-details.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [AsyncPipe, NgIf, ButtonComponent, MatIconModule, TranslateModule], + imports: [ + AsyncPipe, + NgIf, + ButtonComponent, + MatIconModule, + TranslateModule, + CarouselComponent, + BlockListComponent, + LetDirective, + LinkCardComponent, + NgForOf, + PreviousNextButtonsComponent, + UiElementsModule, + UiSearchModule, + MaxLinesComponent, + UiDatavizModule, + RouterLink, + UiWidgetsModule, + ], }) -export class OrganizationDetailsComponent implements OnChanges { +export class OrganizationDetailsComponent + implements OnInit, AfterViewInit, OnDestroy +{ + protected readonly Error = Error + protected readonly ErrorType = ErrorType + @Input() organization: Organization - ngOnChanges(changes: SimpleChanges): void { - console.log(changes['organization']) + lastPublishedDatasets$: Observable = of([]) + + subscriptions$: Subscription = new Subscription() + + isOrganizationsLoading = true + + totalPages = 0 + currentPage = 1 + isFirstPage = this.currentPage === 1 + isLastPage = false + + @ViewChild(BlockListComponent) list: BlockListComponent + + constructor( + private changeDetector: ChangeDetectorRef, + private searchFacade: SearchFacade, + private organizationsService: OrganizationsServiceInterface + ) {} + + ngOnInit(): void { + this.searchFacade.setPageSize(3) + + this.lastPublishedDatasets$ = this.organizationsService + .getFiltersForOrgs([this.organization]) + .pipe( + switchMap((filters) => { + return this.searchFacade + .setFilters(filters) + .setSortBy(['desc', 'changeDate']).results$ + }) + ) + + this.manageSubscriptions() + } + + ngAfterViewInit() { + // this is required to show the pagination correctly + this.changeDetector.detectChanges() + } + + ngOnDestroy(): void { + this.subscriptions$.unsubscribe() + } + + get hasPagination() { + return this.totalPages > 1 + } + + changeStepOrPage(direction: string) { + if (direction === 'next') { + this.searchFacade.paginate(this.currentPage + 1) + } else { + this.searchFacade.paginate(this.currentPage - 1) + } } + + private manageSubscriptions() { + this.subscriptions$.add( + this.searchFacade.isLoading$.subscribe( + (isOrganizationsLoading) => + (this.isOrganizationsLoading = isOrganizationsLoading) + ) + ) + + this.subscriptions$.add( + this.searchFacade.totalPages$.subscribe( + (totalPages) => (this.totalPages = totalPages) + ) + ) + + this.subscriptions$.add( + this.searchFacade.isBeginningOfResults$.subscribe( + (isBeginningOfResults) => (this.isFirstPage = isBeginningOfResults) + ) + ) + + this.subscriptions$.add( + this.searchFacade.isEndOfResults$.subscribe( + (isEndOfResults) => (this.isLastPage = isEndOfResults) + ) + ) + + this.subscriptions$.add( + this.searchFacade.currentPage$.subscribe( + (currentPage) => (this.currentPage = currentPage) + ) + ) + } + + protected readonly ROUTER_ROUTE_SEARCH = ROUTER_ROUTE_SEARCH } diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.html b/apps/datahub/src/app/organization/organization-page/organization-page.component.html index 8af1034ceb..026f5a90df 100644 --- a/apps/datahub/src/app/organization/organization-page/organization-page.component.html +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.html @@ -1,7 +1,11 @@ -
- + + > diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts b/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts index e719f1e097..5068e8b7c4 100644 --- a/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts @@ -1,49 +1,66 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core' +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' - -import { OrganisationsPageComponent } from './organisations-page.component' -import { SearchService } from '@geonetwork-ui/feature/search' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { OrganizationPageComponent } from './organization-page.component' import { of } from 'rxjs' +import { ORGANISATIONS_FIXTURE } from '@geonetwork-ui/common/fixtures' +import { RouterFacade } from '@geonetwork-ui/feature/router' +import { Params } from '@angular/router' +import { TranslateModule } from '@ngx-translate/core' +import { EffectsModule } from '@ngrx/effects' +import { StoreModule } from '@ngrx/store' +import { RouterTestingModule } from '@angular/router/testing' + +const expectedOrganization = ORGANISATIONS_FIXTURE[0] -class SearchServiceMock { - setFilters = jest.fn() +class RouterFacadeMock { + pathParams$ = of({ name: ORGANISATIONS_FIXTURE[0].name } as Params) } -class OrganisationsServiceMock { - getFiltersForOrgs = jest.fn((orgs) => - of({ - orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), - }) - ) +class OrganizationsServiceInterfaceMock { + organisations$ = of(ORGANISATIONS_FIXTURE) } -describe('OrganisationsPageComponent', () => { - let component: OrganisationsPageComponent - let fixture: ComponentFixture - let searchService: SearchService - let orgsService: OrganizationsServiceInterface +describe('OrganizationPageComponent', () => { + let component: OrganizationPageComponent + let fixture: ComponentFixture + let organizationsServiceInterface: OrganizationsServiceInterface beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [OrganisationsPageComponent], + imports: [ + OrganizationPageComponent, + TranslateModule.forRoot({}), + RouterTestingModule, + EffectsModule.forRoot(), + StoreModule.forRoot({}), + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { - provide: SearchService, - useClass: SearchServiceMock, + provide: RouterFacade, + useClass: RouterFacadeMock, }, { provide: OrganizationsServiceInterface, - useClass: OrganisationsServiceMock, + useClass: OrganizationsServiceInterfaceMock, }, ], - }).compileComponents() + }) + .overrideComponent(OrganizationPageComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + imports: [], + schemas: [NO_ERRORS_SCHEMA], + }, + }) + .compileComponents() - searchService = TestBed.inject(SearchService) - orgsService = TestBed.inject(OrganizationsServiceInterface) + organizationsServiceInterface = TestBed.inject( + OrganizationsServiceInterface + ) - fixture = TestBed.createComponent(OrganisationsPageComponent) + fixture = TestBed.createComponent(OrganizationPageComponent) component = fixture.componentInstance fixture.detectChanges() }) @@ -52,22 +69,13 @@ describe('OrganisationsPageComponent', () => { expect(component).toBeTruthy() }) - describe('#searchByOrganisation', () => { + describe('#ngOnInit', () => { beforeEach(() => { - component.searchByOrganisation({ - name: 'MyOrg', - }) + component.ngOnInit() }) - it('generates filters for the org', () => { - expect(orgsService.getFiltersForOrgs).toHaveBeenCalledWith([ - { name: 'MyOrg' }, - ]) - }) - it('updates filters to filter on the org', () => { - expect(searchService.setFilters).toHaveBeenCalledWith({ - orgs: { - MyOrg: true, - }, + it('organization$', () => { + component.organization$.subscribe((org) => { + expect(org).toBe(expectedOrganization) }) }) }) diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.ts b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts index ce9077c94e..a1dc77c981 100644 --- a/apps/datahub/src/app/organization/organization-page/organization-page.component.ts +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts @@ -1,13 +1,14 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' import { RouterFacade } from '@geonetwork-ui/feature/router' -import { AsyncPipe } from '@angular/common' -import { HeaderOrganizationComponent } from '../header-organization/header-organization.component' +import { AsyncPipe, NgIf } from '@angular/common' +import { OrganizationHeaderComponent } from '../header-organization/organization-header.component' import { OrganizationDetailsComponent } from '../organization-details/organization-details.component' import { combineLatest, Observable, of, switchMap } from 'rxjs' import { filter } from 'rxjs/operators' import { Organization } from '@geonetwork-ui/common/domain/model/record' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { LetDirective } from '@ngrx/component' +import { FeatureSearchModule } from '@geonetwork-ui/feature/search' @Component({ selector: 'datahub-organization-page', @@ -17,9 +18,11 @@ import { LetDirective } from '@ngrx/component' standalone: true, imports: [ AsyncPipe, - HeaderOrganizationComponent, + OrganizationHeaderComponent, OrganizationDetailsComponent, LetDirective, + NgIf, + FeatureSearchModule, ], }) export class OrganizationPageComponent implements OnInit { @@ -36,13 +39,12 @@ export class OrganizationPageComponent implements OnInit { this.orgService.organisations$, ]).pipe( filter(([pathParams, _]) => Object.keys(pathParams).length > 0), - switchMap(([pathParams, organizations]) => - of( - organizations.find( - (organization) => organization.name === pathParams['name'] - ) + switchMap(([pathParams, organizations]) => { + const organization = organizations.find( + (organization) => organization.name === pathParams['name'] ) - ) + return of(organization) + }) ) } } diff --git a/apps/datahub/src/app/router/datahub-router.service.spec.ts b/apps/datahub/src/app/router/datahub-router.service.spec.ts index 547369032a..f34c7cdedc 100644 --- a/apps/datahub/src/app/router/datahub-router.service.spec.ts +++ b/apps/datahub/src/app/router/datahub-router.service.spec.ts @@ -7,6 +7,8 @@ import { SearchPageComponent } from '../home/search/search-page/search-page.comp import { RecordPageComponent } from '../record/record-page/record-page.component' import { DatahubRouterService } from './datahub-router.service' +import { ROUTER_ROUTE_ORGANIZATION } from '@geonetwork-ui/feature/router' +import { OrganizationPageComponent } from '../organization/organization-page/organization-page.component' const RouterMock = { resetConfig: jest.fn(), @@ -57,6 +59,13 @@ const expectedRoutes = [ path: `dataset/:metadataUuid`, component: RecordPageComponent, }, + { + path: `${ROUTER_ROUTE_ORGANIZATION}/:name`, + component: OrganizationPageComponent, + data: { + shouldDetach: true, + }, + }, { path: '**', redirectTo: '', pathMatch: 'full' }, ] describe('DatahubRouterService', () => { diff --git a/apps/datahub/src/styles.css b/apps/datahub/src/styles.css index 245c168a2d..f290ad9d70 100644 --- a/apps/datahub/src/styles.css +++ b/apps/datahub/src/styles.css @@ -16,9 +16,6 @@ body { .container-lg { max-width: 1024px; } -.container-xl { - max-width: 1440px; -} html.record-page-active { scroll-padding-top: 220px; diff --git a/apps/metadata-editor/src/app/app.module.ts b/apps/metadata-editor/src/app/app.module.ts index ec73edecd0..db79237697 100644 --- a/apps/metadata-editor/src/app/app.module.ts +++ b/apps/metadata-editor/src/app/app.module.ts @@ -45,6 +45,7 @@ import { FeatureEditorModule } from '@geonetwork-ui/feature/editor' searchStateId: 'editor', searchRouteComponent: DashboardPageComponent, recordRouteComponent: null, + organizationRouteComponent: null, }), ...extModules, ], diff --git a/libs/common/fixtures/src/lib/organisations.fixture.ts b/libs/common/fixtures/src/lib/organisations.fixture.ts index 96734f1aab..a2c5818b36 100644 --- a/libs/common/fixtures/src/lib/organisations.fixture.ts +++ b/libs/common/fixtures/src/lib/organisations.fixture.ts @@ -5,84 +5,112 @@ export const ORGANISATIONS_FIXTURE: Organization[] = deepFreeze([ { name: 'I Data Org', description: 'one org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo1.png'), recordCount: 12, }, { name: 'H Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo2.png'), recordCount: 15, }, { name: 'J Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo3.png'), recordCount: 6, }, { name: 'G Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo4.png'), recordCount: 8, }, { name: 'B Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo5.png'), recordCount: 2, }, { name: 'D Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo6.png'), recordCount: 17, }, { name: 'F Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo7.png'), recordCount: 14, }, { name: 'A Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo8.png'), recordCount: 3, }, { name: 'C Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo9.png'), recordCount: 9, }, { name: 'E Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo10.png'), recordCount: 1, }, { name: 'é Data Org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo10.png'), recordCount: 2, }, { name: 'wizard-org', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo11.png'), recordCount: 2, }, { name: "Université de l'Ingénierie", description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo12.png'), recordCount: 2, }, { name: 'ARS / Agence régionale de santé', description: 'another org for testing', + email: 'test@gmail.com', + website: new URL('https://my-geonetwork.org/'), logoUrl: new URL('https://my-geonetwork.org/logo12.png'), recordCount: 2, }, diff --git a/libs/ui/elements/src/lib/error/error.component.html b/libs/ui/elements/src/lib/error/error.component.html index b145a4caf1..0b0c302281 100644 --- a/libs/ui/elements/src/lib/error/error.component.html +++ b/libs/ui/elements/src/lib/error/error.component.html @@ -32,6 +32,16 @@
search.error.receivedError
{{ error }}
+
+
+ computer + question_mark +
+
search.error.organizationHasNoDataset
+
+
Date: Fri, 24 May 2024 15:30:29 +0200 Subject: [PATCH 07/10] fix(datahub): fix organization preview component --- .../organisation-preview/organisation-preview.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html index da523e26c3..711122dffb 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.html @@ -33,7 +33,9 @@ {{ organization.recordCount }} - record.metadata.publications + record.metadata.publications
From 2db515a828f5637c77884e75c781eaced37fbb1c Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:31:08 +0200 Subject: [PATCH 08/10] feat(datahub): added isBeginningOfResults$ observable in the search facade --- .../search/src/lib/state/search.facade.ts | 5 +++ .../search/src/lib/state/selectors.spec.ts | 32 +++++++++++++++++++ .../feature/search/src/lib/state/selectors.ts | 7 ++++ 3 files changed, 44 insertions(+) diff --git a/libs/feature/search/src/lib/state/search.facade.ts b/libs/feature/search/src/lib/state/search.facade.ts index 9e3046a73e..1f9388a2b1 100644 --- a/libs/feature/search/src/lib/state/search.facade.ts +++ b/libs/feature/search/src/lib/state/search.facade.ts @@ -37,6 +37,7 @@ import { getSearchResultsLoading, getSearchSortBy, getSpatialFilterEnabled, + isBeginningOfResults, isEndOfResults, totalPages, } from './selectors' @@ -58,6 +59,7 @@ export class SearchFacade { layout$: Observable sortBy$: Observable isLoading$: Observable + isBeginningOfResults$: Observable isEndOfResults$: Observable totalPages$: Observable currentPage$: Observable @@ -98,6 +100,9 @@ export class SearchFacade { this.isLoading$ = this.store.pipe(select(getSearchResultsLoading, searchId)) this.searchFilters$ = this.store.pipe(select(getSearchFilters, searchId)) this.resultsHits$ = this.store.pipe(select(getSearchResultsHits, searchId)) + this.isBeginningOfResults$ = this.store.pipe( + select(isBeginningOfResults, searchId) + ) this.isEndOfResults$ = this.store.pipe(select(isEndOfResults, searchId)) this.totalPages$ = this.store.pipe(select(totalPages, searchId)) this.currentPage$ = this.store.pipe(select(currentPage, searchId)) diff --git a/libs/feature/search/src/lib/state/selectors.spec.ts b/libs/feature/search/src/lib/state/selectors.spec.ts index d20bfb6156..70127f68c0 100644 --- a/libs/feature/search/src/lib/state/selectors.spec.ts +++ b/libs/feature/search/src/lib/state/selectors.spec.ts @@ -76,6 +76,38 @@ describe('Search Selectors', () => { }) }) + describe('isBeginningOfResults', () => { + it('should return true once at the beginning of results list', () => { + const beginningResult = fromSelectors.isBeginningOfResults.projector({ + ...initialStateSearch, + params: { + ...initialStateSearch.params, + currentPage: 0, + pageSize: 20, + }, + results: { + ...initialStateSearch.results, + count: 62, + }, + }) + expect(beginningResult).toEqual(true) + + const notBeginningResult = fromSelectors.isBeginningOfResults.projector({ + ...initialStateSearch, + params: { + ...initialStateSearch.params, + currentPage: 3, + pageSize: 20, + }, + results: { + ...initialStateSearch.results, + count: 62, + }, + }) + expect(notBeginningResult).toEqual(false) + }) + }) + describe('isEndOfResults', () => { it('should return true once at the end of results list', () => { const result = fromSelectors.isEndOfResults.projector({ diff --git a/libs/feature/search/src/lib/state/selectors.ts b/libs/feature/search/src/lib/state/selectors.ts index c8f85088c3..b2215ba3fc 100644 --- a/libs/feature/search/src/lib/state/selectors.ts +++ b/libs/feature/search/src/lib/state/selectors.ts @@ -50,6 +50,13 @@ export const getSearchResultsHits = createSelector( (state: SearchStateSearch) => state.results.count ) +export const isBeginningOfResults = createSelector( + getSearchStateSearch, + (state: SearchStateSearch) => { + return state.params.currentPage === 0 + } +) + export const isEndOfResults = createSelector( getSearchStateSearch, (state: SearchStateSearch) => { From 96cc908ab435b52ab3dd2ddcf3ca6c97848c5903 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:31:27 +0200 Subject: [PATCH 09/10] refactor(datahub): refactor --- .../app/records/records-count/records-count.component.ts | 7 +------ .../gn4/organizations/organizations-from-groups.service.ts | 1 + .../organizations/organizations-from-metadata.service.ts | 4 +++- libs/feature/catalog/src/lib/organization-url.token.ts | 4 +++- .../organisation-preview/organisation-preview.component.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/metadata-editor/src/app/records/records-count/records-count.component.ts b/apps/metadata-editor/src/app/records/records-count/records-count.component.ts index 2b500daf90..65f5635c28 100644 --- a/apps/metadata-editor/src/app/records/records-count/records-count.component.ts +++ b/apps/metadata-editor/src/app/records/records-count/records-count.component.ts @@ -1,9 +1,4 @@ -import { - Component, - EventEmitter, - importProvidersFrom, - Output, -} from '@angular/core' +import { Component, EventEmitter, Output } from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts index b6a8a39493..8ccaecb521 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts @@ -76,6 +76,7 @@ export class OrganizationsFromGroupsService return { name: group.label[lang3], ...(group.description && { description: group.description }), + ...(group.email && { email: group.email }), ...(group.logo && { logoUrl: getAsUrl(`${IMAGE_URL}${group.logo}`), }), diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts index d218eac915..9ebf479652 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts @@ -230,10 +230,12 @@ export class OrganizationsFromMetadataService if (!group) return fullOrg return { ...fullOrg, + email: emails[0], ...(group.description && { description: group.description }), ...(group.logo && { logoUrl: getAsUrl(`${IMAGE_URL}${group.logo}`) }), ...(group.website && { website: getAsUrl(group.website) }), - } + ...(group.email && { email: group.email }), + } as Organization }) } diff --git a/libs/feature/catalog/src/lib/organization-url.token.ts b/libs/feature/catalog/src/lib/organization-url.token.ts index b3f5739b26..91deb6fdb8 100644 --- a/libs/feature/catalog/src/lib/organization-url.token.ts +++ b/libs/feature/catalog/src/lib/organization-url.token.ts @@ -1,4 +1,6 @@ import { InjectionToken } from '@angular/core' // expects the replacement key ${name} -export const ORGANIZATION_PAGE_URL_TOKEN = new InjectionToken('organization-page-url-token') +export const ORGANIZATION_PAGE_URL_TOKEN = new InjectionToken( + 'organization-page-url-token' +) diff --git a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts index d44c8e3d94..c8eae909ce 100644 --- a/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts +++ b/libs/ui/catalog/src/lib/organisation-preview/organisation-preview.component.ts @@ -18,7 +18,7 @@ export class OrganisationPreviewComponent { @Input() organisationUrl: string @Output() clickedOrganisation = new EventEmitter() - clickOrganisation(event: Event) { + clickOrganisation(event: Event) { event.preventDefault() this.clickedOrganisation.emit(this.organization) } From 27816fdb1b82141527bac1039eab0ffe4349cf7c Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Fri, 24 May 2024 15:34:11 +0200 Subject: [PATCH 10/10] feat(datahub): finish organization-page --- .../src/e2e/organization-page.cy.ts | 118 ++++++++++ apps/datahub-e2e/src/e2e/organizations.cy.ts | 9 +- apps/datahub/src/app/app.module.ts | 2 +- .../organization-header.component.html | 54 ----- .../organization-details.component.css | 15 ++ .../organization-details.component.html | 211 ++++++++++-------- .../organization-details.component.spec.ts | 96 +++----- .../organization-details.component.ts | 111 +++++---- .../organization-header.component.css | 0 .../organization-header.component.html | 60 +++++ .../organization-header.component.spec.ts | 0 .../organization-header.component.ts | 13 +- .../organization-page.component.ts | 2 +- .../organizations-from-groups.service.spec.ts | 3 + ...rganizations-from-metadata.service.spec.ts | 7 +- .../domain/src/lib/model/user/user.model.ts | 2 +- libs/feature/catalog/src/index.ts | 1 + .../catalog/src/lib/my-org/my-org.service.ts | 4 +- .../organisations.component.spec.ts | 18 +- .../src/lib/default/router.service.spec.ts | 16 +- .../src/lib/error/error.component.html | 32 ++- .../elements/src/lib/error/error.component.ts | 1 + .../related-record-card.component.html | 2 +- .../related-record-card.component.ts | 23 +- .../lib/max-lines/max-lines.component.spec.ts | 13 +- .../max-lines/max-lines.component.stories.ts | 2 +- .../docker-entrypoint-initdb.d/dump | Bin 460488 -> 460655 bytes 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 + 35 files changed, 536 insertions(+), 295 deletions(-) create mode 100644 apps/datahub-e2e/src/e2e/organization-page.cy.ts delete mode 100644 apps/datahub/src/app/organization/header-organization/organization-header.component.html rename apps/datahub/src/app/organization/{header-organization => organization-header}/organization-header.component.css (100%) create mode 100644 apps/datahub/src/app/organization/organization-header/organization-header.component.html rename apps/datahub/src/app/organization/{header-organization => organization-header}/organization-header.component.spec.ts (100%) rename apps/datahub/src/app/organization/{header-organization => organization-header}/organization-header.component.ts (75%) diff --git a/apps/datahub-e2e/src/e2e/organization-page.cy.ts b/apps/datahub-e2e/src/e2e/organization-page.cy.ts new file mode 100644 index 0000000000..5cddc7935c --- /dev/null +++ b/apps/datahub-e2e/src/e2e/organization-page.cy.ts @@ -0,0 +1,118 @@ +import 'cypress-real-events' + +describe('organizations', () => { + beforeEach(() => { + cy.visit('organization/Barbie%20Inc.') + + // aliases + cy.get('gn-ui-navigation-button').as('backButton') + cy.get('[data-test="organizationHeaderName"]').as('organizationHeaderName') + cy.get('[data-test="organizationHeaderWebsiteLink"]').as( + 'organizationHeaderWebsiteLink' + ) + cy.get('[data-test="organizationDescription"]').as( + 'organizationDescription' + ) + cy.get('gn-ui-max-lines').contains('Read more').as('readMoreButton') + cy.get('[data-test="organizationLogo"]').as('organizationLogo') + cy.get('[data-test="organizationDatasetCount"]').as( + 'organizationDatasetCount' + ) + cy.get('[data-test="organizationEmail"]').as('organizationEmail') + cy.get('[data-test="orgPageLasPubDat"]').as('orgPageLasPubDat') + cy.get('[data-test="orgDetailsSearchAllBtn"]').as('orgDetailsSearchAllBtn') + }) + + describe('general display', () => { + describe('header', () => { + describe('back button', () => { + beforeEach(() => { + cy.visit('organisations') + cy.visit('organization/Barbie%20Inc.') + }) + + it('back button goes to the previous visited page', () => { + cy.get('@backButton').click() + cy.url().should('include', '/organisations') + }) + }) + + it('should display the organization name', () => { + cy.get('@organizationHeaderName').should('contain', 'Barbie Inc.') + }) + + it('should display the organization website link', () => { + cy.get('@organizationHeaderWebsiteLink') + .should('be.visible') + .should('have.attr', 'href', 'https://www.barbie-inc.com/') + .and('have.attr', 'target', '_blank') + }) + }) + + describe('details', () => { + describe('left column', () => { + it('should display the organization description', () => { + cy.get('@organizationDescription').should('be.visible') + }) + + it('click on read more should expand the organization description', () => { + let initialDescription + let newDescription + + cy.get('@organizationDescription').then((firstDescription) => { + initialDescription = firstDescription + cy.get('@readMoreButton').trigger('click') + cy.get('@organizationDescription').then((secondDescription) => { + newDescription = secondDescription + expect(newDescription).to.not.equal(initialDescription) + }) + }) + }) + }) + + describe('right column', () => { + it('should display the organization logo', () => { + cy.get('@organizationLogo').should('be.visible') + }) + + it('should display the organization dataset count', () => { + cy.get('@organizationDatasetCount').should('be.visible') + }) + + it('a click on the organization dataset count should open the dataset search page filtered on the organization', () => { + cy.get('@organizationDatasetCount').then(($link) => { + const url = $link.prop('href') + cy.wrap($link).click() + + cy.url().should('eq', url) + }) + }) + + it('should display the organization email', () => { + cy.get('@organizationEmail') + .should('be.visible') + .and('have.attr', 'href', 'mailto:contact@barbie-inc.com') + }) + }) + + describe('last published datasets', () => { + it('should display the last published datasets', () => { + cy.get('@orgPageLasPubDat').should('be.visible') + }) + + it('should display the search all button', () => { + cy.get('@orgDetailsSearchAllBtn').should('be.visible') + }) + + it('a click on the search all button should open the dataset search page filtered on the organization', () => { + cy.get('@orgDetailsSearchAllBtn').then(($link) => { + const url = $link.prop('href') + cy.wrap($link).click() + + cy.url().should('eq', url) + }) + }) + }) + }) + }) +}) diff --git a/apps/datahub-e2e/src/e2e/organizations.cy.ts b/apps/datahub-e2e/src/e2e/organizations.cy.ts index b39da4ca39..ded2dc026d 100644 --- a/apps/datahub-e2e/src/e2e/organizations.cy.ts +++ b/apps/datahub-e2e/src/e2e/organizations.cy.ts @@ -77,14 +77,15 @@ describe('organizations', () => { }) describe('list features', () => { - it('should search with a filter on the selected org on click', () => { + it('should open the organization page', () => { cy.get('@organizationsName') .eq(10) .then(($clickedName) => { cy.get('@organizations').eq(10).click() - cy.url() - .should('include', 'publisher=') - .and('include', encodeURIComponent($clickedName.text().trim())) + cy.url().should( + 'contain', + `organization/${encodeURIComponent($clickedName.text().trim())}` + ) }) }) }) diff --git a/apps/datahub/src/app/app.module.ts b/apps/datahub/src/app/app.module.ts index 534338f09d..0c69562559 100644 --- a/apps/datahub/src/app/app.module.ts +++ b/apps/datahub/src/app/app.module.ts @@ -5,6 +5,7 @@ import { BrowserModule } from '@angular/platform-browser' import { Router, RouterModule } from '@angular/router' import { FeatureCatalogModule, + ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, } from '@geonetwork-ui/feature/catalog' import { @@ -94,7 +95,6 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { RecordUserFeedbacksComponent } from './record/record-user-feedbacks/record-user-feedbacks.component' import { LetDirective } from '@ngrx/component' import { OrganizationPageComponent } from './organization/organization-page/organization-page.component' -import { ORGANIZATION_PAGE_URL_TOKEN } from '../../../../libs/feature/catalog/src/lib/organization-url.token' export const metaReducers: MetaReducer[] = !environment.production ? [] : [] diff --git a/apps/datahub/src/app/organization/header-organization/organization-header.component.html b/apps/datahub/src/app/organization/header-organization/organization-header.component.html deleted file mode 100644 index 9c16ff9bc0..0000000000 --- a/apps/datahub/src/app/organization/header-organization/organization-header.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
-
-
- - -
-
- -
-
-
- {{ organization.name }} -
-
- folder -

- {{ organization.recordCount }} -

-

- organization.header.recordCount -

- -

- - {{ organization.website.href }} - open_in_new -
-
-
-
diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.css b/apps/datahub/src/app/organization/organization-details/organization-details.component.css index e69de29bb2..c0d50f2d1c 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.css +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.css @@ -0,0 +1,15 @@ +.list-page-dot { + width: 6px; + height: 6px; + border-radius: 6px; + position: relative; +} + +.list-page-dot:after { + content: ''; + position: absolute; + left: -7px; + top: -7px; + width: 20px; + height: 20px; +} diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.html b/apps/datahub/src/app/organization/organization-details/organization-details.component.html index 9ef5da0aef..d08616ad65 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.html +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.html @@ -1,117 +1,108 @@ - -
-
-
- - - -
-
- -
- -
-
+ + +
+
+
+
+
+ +
+ +
+
+
-
- - - - -
- - -
- - + +
+ - + [routerLink]="['/', ROUTER_ROUTE_SEARCH]" + [queryParams]="{ publisher: organization.name }" + > + + - - organization.details.mailContact - - + +
-
-
-
-
-
-

- organization.lastPublishedDatasets -

- +
+
+
+
+

+ organization.lastPublishedDatasets +

+ - - -
+
-
- +
+
+ +
- - -
- -
-
+ +
+ +
+
- - - + + + +
-
+ + + + + diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts index 6e4d6078ac..bec7bfd76b 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts @@ -52,10 +52,6 @@ class OrganisationsServiceMock { } const anOrganizationWithManyDatasets: Organization = ORGANISATIONS_FIXTURE[0] -const anOrganizationWithOnlyOneDataset: Organization = { - ...ORGANISATIONS_FIXTURE[0], - recordCount: 1, -} const oneDataset = [DATASET_RECORDS[0]] const manyDatasets = DATASET_RECORDS.concat(DATASET_RECORDS[0]) @@ -204,14 +200,9 @@ describe('OrganizationDetailsComponent', () => { organizationIsLoading.next(true) fixture.detectChanges() - const organizationDetailsLastPublishedDatasetsPreviousNextButtons = - getHTMLElement( - 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' - ) + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') - expect( - organizationDetailsLastPublishedDatasetsPreviousNextButtons - ).toBeFalsy() + expect(orgDetailsNavBtn).toBeFalsy() }) it('should not be displayed organization is loaded but has no pagination', () => { @@ -219,14 +210,9 @@ describe('OrganizationDetailsComponent', () => { totalPages.next(1) fixture.detectChanges() - const organizationDetailsLastPublishedDatasetsPreviousNextButtons = - getHTMLElement( - 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' - ) + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') - expect( - organizationDetailsLastPublishedDatasetsPreviousNextButtons - ).toBeFalsy() + expect(orgDetailsNavBtn).toBeFalsy() }) it('should be displayed if organization is loadded and have pagination', () => { @@ -234,39 +220,29 @@ describe('OrganizationDetailsComponent', () => { totalPages.next(10) fixture.detectChanges() - const organizationDetailsLastPublishedDatasetsPreviousNextButtons = - getHTMLElement( - 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' - ) + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') - expect( - organizationDetailsLastPublishedDatasetsPreviousNextButtons - ).toBeTruthy() + expect(orgDetailsNavBtn).toBeTruthy() }) it('should call paginate from the facade if button is clicked', () => { const initialPageNumber = currentPage.getValue() const nextPageNumber = initialPageNumber + 1 - const organizationDetailsLastPublishedDatasetsPreviousNextButtons = - getHTMLElement( - 'organizationDetailsLastPublishedDatasetsPreviousNextButtons' - ) + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') - const nextButton = - organizationDetailsLastPublishedDatasetsPreviousNextButtons?.querySelector( - '[data-test="nextButton"]' - ) as HTMLElement + const nextButton = orgDetailsNavBtn?.querySelector( + '[data-test="nextButton"]' + ) as HTMLElement ;(nextButton?.firstChild as HTMLElement).click() fixture.detectChanges() expect(searchFacade.paginate).toHaveBeenCalledWith(nextPageNumber) - const previousButton = - organizationDetailsLastPublishedDatasetsPreviousNextButtons?.querySelector( - '[data-test="previousButton"]' - ) as HTMLElement + const previousButton = orgDetailsNavBtn?.querySelector( + '[data-test="previousButton"]' + ) as HTMLElement ;(previousButton?.firstChild as HTMLElement).click() fixture.detectChanges() @@ -276,20 +252,13 @@ describe('OrganizationDetailsComponent', () => { describe('Search all button', () => { it('should send to the search page filtered on the correct organization', () => { - const organizationDetailsLastPublishedDatasetsSearchAllButton = - getHTMLElement( - 'organizationDetailsLastPublishedDatasetsSearchAllButton' - ) - - expect( - organizationDetailsLastPublishedDatasetsSearchAllButton - ).toBeTruthy() - - expect( - organizationDetailsLastPublishedDatasetsSearchAllButton?.getAttribute( - 'href' - ) - ).toEqual( + const orgDetailsSearchAllBtn = getHTMLElement( + 'orgDetailsSearchAllBtn' + ) + + expect(orgDetailsSearchAllBtn).toBeTruthy() + + expect(orgDetailsSearchAllBtn?.getAttribute('href')).toEqual( `/${ROUTER_ROUTE_SEARCH}?publisher=${encodeURIComponent( anOrganizationWithManyDatasets.name )}` @@ -300,21 +269,26 @@ describe('OrganizationDetailsComponent', () => { describe('Last published datasets', () => { it('should display the datasets properly', () => { - const organizationPageLastPublishedDatasets = getHTMLElement( - 'organizationPageLastPublishedDatasets' - ) + const orgPageLasPubDat = getHTMLElement('orgPageLasPubDat') - expect(organizationPageLastPublishedDatasets).toBeTruthy() - expect(organizationPageLastPublishedDatasets?.children.length).toEqual( - desiredPageSize - ) + expect(orgPageLasPubDat).toBeTruthy() + expect(orgPageLasPubDat?.children.length).toEqual(desiredPageSize) results.next(oneDataset) fixture.detectChanges() - expect(organizationPageLastPublishedDatasets?.children.length).toEqual( - 1 - ) + expect(orgPageLasPubDat?.children.length).toEqual(1) + }) + + it('should display the orgHasNodataset error component if the org has no dataset', () => { + results.next([]) + fixture.detectChanges() + + const orgHasNoDataset = getHTMLElement('lastPubliDatasets') + + console.log(orgHasNoDataset?.outerHTML) + + expect(orgHasNoDataset).toBeTruthy() }) }) }) diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts index 23ab3fd7a2..2656219bee 100644 --- a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts @@ -4,11 +4,13 @@ import { ChangeDetectorRef, Component, Input, + OnChanges, OnDestroy, OnInit, + SimpleChanges, ViewChild, } from '@angular/core' -import { AsyncPipe, NgForOf, NgIf } from '@angular/common' +import { AsyncPipe, NgClass, NgForOf, NgIf } from '@angular/common' import { CatalogRecord, Organization, @@ -32,7 +34,15 @@ import { } from '@geonetwork-ui/ui/elements' import { UiSearchModule } from '@geonetwork-ui/ui/search' import { SearchFacade } from '@geonetwork-ui/feature/search' -import { Observable, of, Subscription, switchMap } from 'rxjs' +import { + BehaviorSubject, + combineLatest, + distinctUntilChanged, + Observable, + of, + Subscription, + switchMap, +} from 'rxjs' import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz' import { RouterLink } from '@angular/router' import { ROUTER_ROUTE_SEARCH } from '@geonetwork-ui/feature/router' @@ -63,27 +73,36 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' UiDatavizModule, RouterLink, UiWidgetsModule, + NgClass, ], }) export class OrganizationDetailsComponent - implements OnInit, AfterViewInit, OnDestroy + implements OnInit, AfterViewInit, OnDestroy, OnChanges { protected readonly Error = Error protected readonly ErrorType = ErrorType + protected readonly ROUTER_ROUTE_SEARCH = ROUTER_ROUTE_SEARCH - @Input() organization: Organization + protected get pages() { + return new Array(this.totalPages).fill(0).map((_, i) => i + 1) + } lastPublishedDatasets$: Observable = of([]) subscriptions$: Subscription = new Subscription() - isOrganizationsLoading = true + isSearchFacadeLoading = true totalPages = 0 currentPage = 1 isFirstPage = this.currentPage === 1 isLastPage = false + organizationHasChanged$ = new BehaviorSubject(undefined) + + @Input() organization?: Organization + @Input() paginationContainerClass = 'w-full bottom-0 top-auto' + @ViewChild(BlockListComponent) list: BlockListComponent constructor( @@ -95,19 +114,30 @@ export class OrganizationDetailsComponent ngOnInit(): void { this.searchFacade.setPageSize(3) - this.lastPublishedDatasets$ = this.organizationsService - .getFiltersForOrgs([this.organization]) - .pipe( - switchMap((filters) => { - return this.searchFacade - .setFilters(filters) - .setSortBy(['desc', 'changeDate']).results$ - }) - ) + this.lastPublishedDatasets$ = this.organizationHasChanged$.pipe( + distinctUntilChanged(), + switchMap(() => { + return this.organizationsService + .getFiltersForOrgs([this.organization]) + .pipe( + switchMap((filters) => { + return this.searchFacade + .setFilters(filters) + .setSortBy(['desc', 'changeDate']).results$ + }) + ) + }) + ) this.manageSubscriptions() } + ngOnChanges(changes: SimpleChanges): void { + if (changes['organization']) { + this.organizationHasChanged$.next() + } + } + ngAfterViewInit() { // this is required to show the pagination correctly this.changeDetector.detectChanges() @@ -129,38 +159,35 @@ export class OrganizationDetailsComponent } } - private manageSubscriptions() { - this.subscriptions$.add( - this.searchFacade.isLoading$.subscribe( - (isOrganizationsLoading) => - (this.isOrganizationsLoading = isOrganizationsLoading) - ) - ) - - this.subscriptions$.add( - this.searchFacade.totalPages$.subscribe( - (totalPages) => (this.totalPages = totalPages) - ) - ) - - this.subscriptions$.add( - this.searchFacade.isBeginningOfResults$.subscribe( - (isBeginningOfResults) => (this.isFirstPage = isBeginningOfResults) - ) - ) - - this.subscriptions$.add( - this.searchFacade.isEndOfResults$.subscribe( - (isEndOfResults) => (this.isLastPage = isEndOfResults) - ) - ) + goToPage(page: number) { + this.searchFacade.paginate(page) + } + private manageSubscriptions() { this.subscriptions$.add( - this.searchFacade.currentPage$.subscribe( - (currentPage) => (this.currentPage = currentPage) + combineLatest([ + this.searchFacade.isLoading$.pipe(distinctUntilChanged()), + this.searchFacade.totalPages$.pipe(distinctUntilChanged()), + this.searchFacade.isBeginningOfResults$.pipe(distinctUntilChanged()), + this.searchFacade.isEndOfResults$.pipe(distinctUntilChanged()), + this.searchFacade.currentPage$.pipe(distinctUntilChanged()), + ]).subscribe( + ([ + isSearchFacadeLoading, + totalPages, + isBeginningOfResults, + isEndOfResults, + currentPage, + ]) => { + this.isSearchFacadeLoading = isSearchFacadeLoading + this.totalPages = totalPages + this.isFirstPage = isBeginningOfResults + this.isLastPage = isEndOfResults + this.currentPage = currentPage + } ) ) } - protected readonly ROUTER_ROUTE_SEARCH = ROUTER_ROUTE_SEARCH + protected readonly errorTypes = ErrorType } diff --git a/apps/datahub/src/app/organization/header-organization/organization-header.component.css b/apps/datahub/src/app/organization/organization-header/organization-header.component.css similarity index 100% rename from apps/datahub/src/app/organization/header-organization/organization-header.component.css rename to apps/datahub/src/app/organization/organization-header/organization-header.component.css diff --git a/apps/datahub/src/app/organization/organization-header/organization-header.component.html b/apps/datahub/src/app/organization/organization-header/organization-header.component.html new file mode 100644 index 0000000000..42e90a94bd --- /dev/null +++ b/apps/datahub/src/app/organization/organization-header/organization-header.component.html @@ -0,0 +1,60 @@ +
+
+
+
+ + +
+
+ +
+
+ +
+ {{ organization.name }} +
+
+ folder +

+ {{ organization.recordCount }} +

+

+ organization.header.recordCount +

+ +

+ + {{ organization.website.href }} + open_in_new +
+
+
+
+
diff --git a/apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts b/apps/datahub/src/app/organization/organization-header/organization-header.component.spec.ts similarity index 100% rename from apps/datahub/src/app/organization/header-organization/organization-header.component.spec.ts rename to apps/datahub/src/app/organization/organization-header/organization-header.component.spec.ts diff --git a/apps/datahub/src/app/organization/header-organization/organization-header.component.ts b/apps/datahub/src/app/organization/organization-header/organization-header.component.ts similarity index 75% rename from apps/datahub/src/app/organization/header-organization/organization-header.component.ts rename to apps/datahub/src/app/organization/organization-header/organization-header.component.ts index 58c51e903f..8e62fd986d 100644 --- a/apps/datahub/src/app/organization/header-organization/organization-header.component.ts +++ b/apps/datahub/src/app/organization/organization-header/organization-header.component.ts @@ -6,6 +6,8 @@ import { UiCatalogModule } from '@geonetwork-ui/ui/catalog' import { Organization } from '@geonetwork-ui/common/domain/model/record' import { AsyncPipe, Location, NgIf } from '@angular/common' import { MatIconModule } from '@angular/material/icon' +import { ErrorType, UiElementsModule } from '@geonetwork-ui/ui/elements' +import { Router } from '@angular/router' @Component({ selector: 'datahub-organization-header', @@ -20,10 +22,11 @@ import { MatIconModule } from '@angular/material/icon' NgIf, MatIconModule, AsyncPipe, + UiElementsModule, ], }) export class OrganizationHeaderComponent { - @Input() organization: Organization + @Input() organization?: Organization backgroundCss = getThemeConfig().HEADER_BACKGROUND || @@ -31,9 +34,13 @@ export class OrganizationHeaderComponent { foregroundColor = getThemeConfig().HEADER_FOREGROUND_COLOR || '#ffffff' showLanguageSwitcher = getGlobalConfig().LANGUAGES?.length > 0 - constructor(private location: Location) {} + constructor(private location: Location, private router: Router) {} back() { - this.location.back() + this.organization + ? this.location.back() + : this.router.navigateByUrl('/organisations') } + + protected readonly errorTypes = ErrorType } diff --git a/apps/datahub/src/app/organization/organization-page/organization-page.component.ts b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts index a1dc77c981..99cc91b16b 100644 --- a/apps/datahub/src/app/organization/organization-page/organization-page.component.ts +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' import { RouterFacade } from '@geonetwork-ui/feature/router' import { AsyncPipe, NgIf } from '@angular/common' -import { OrganizationHeaderComponent } from '../header-organization/organization-header.component' +import { OrganizationHeaderComponent } from '../organization-header/organization-header.component' import { OrganizationDetailsComponent } from '../organization-details/organization-details.component' import { combineLatest, Observable, of, switchMap } from 'rxjs' import { filter } from 'rxjs/operators' diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.spec.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.spec.ts index fe4c0ad104..dd77d0a52c 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.spec.ts @@ -50,12 +50,14 @@ const sampleOrgA: Organization = { recordCount: 80, description: 'A description for Köniz Municipality', website: new URL('https://www.koeniz.ch/'), + email: 'reto.jau@koeniz.ch', } const sampleOrgB: Organization = { logoUrl: new URL('http://localhost/geonetwork/images/harvesting/bakom.png'), name: 'Office fédéral de la communication OFCOM', recordCount: 50, website: new URL('http://www.bakom.admin.ch/'), + email: 'christian.meier@bakom.admin.ch', } const sampleOrgC: Organization = { logoUrl: new URL( @@ -65,6 +67,7 @@ const sampleOrgC: Organization = { recordCount: 20, description: 'A description for ARE', website: new URL('http://www.are.admin.ch/'), + email: 'rolf.giezendanner@are.admin.ch', } class SearchApiServiceMock { diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts index 10fb3101b8..5f17e9e1d7 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts @@ -25,21 +25,22 @@ const sampleOrgA: Organization = { name: 'ARE', recordCount: 5, website: new URL('http://www.are.admin.ch/'), + email: 'rolf.giezendanner@are.admin.ch', } const sampleOrgB: Organization = { logoUrl: new URL('http://localhost/geonetwork/images/harvesting/bakom.png'), name: 'BAKOM', recordCount: 2, website: new URL('http://www.bakom.admin.ch/'), + email: 'christian.meier@bakom.admin.ch', } const sampleOrgC: Organization = { - logoUrl: new URL( - 'http://localhost/geonetwork/images/harvesting/ifremer-org.png' - ), + logoUrl: new URL('http://localhost/geonetwork/images/harvesting/ifremer.png'), name: 'Ifremer', recordCount: 1, description: "Institut français de recherche pour l'exploitation de la mer", website: new URL('https://www.ifremer.fr/'), + email: 'ifremer.ifremer@ifremer.admin.fr', } let geonetworkVersion: string diff --git a/libs/common/domain/src/lib/model/user/user.model.ts b/libs/common/domain/src/lib/model/user/user.model.ts index fefd657049..591fc7bf18 100644 --- a/libs/common/domain/src/lib/model/user/user.model.ts +++ b/libs/common/domain/src/lib/model/user/user.model.ts @@ -5,6 +5,6 @@ export interface UserModel { name: string surname: string email: string - organization: string + organisation: string profileIcon?: string } diff --git a/libs/feature/catalog/src/index.ts b/libs/feature/catalog/src/index.ts index efa22c7852..54cc255a57 100644 --- a/libs/feature/catalog/src/index.ts +++ b/libs/feature/catalog/src/index.ts @@ -1,4 +1,5 @@ export * from './lib/feature-catalog.module' +export * from './lib/organization-url.token' export * from './lib/sources/sources.service' export * from './lib/sources/sources.model' export * from './lib/records/records.service' diff --git a/libs/feature/catalog/src/lib/my-org/my-org.service.ts b/libs/feature/catalog/src/lib/my-org/my-org.service.ts index 324884f5a7..8840cc3bba 100644 --- a/libs/feature/catalog/src/lib/my-org/my-org.service.ts +++ b/libs/feature/catalog/src/lib/my-org/my-org.service.ts @@ -27,12 +27,12 @@ export class MyOrgService { this.orgService.organisations$, ]).pipe( map(([user, allUsers, orgs]) => { - const orgName = user.organization + const orgName = user.organisation const org = orgs.find((org) => org.name === orgName) const logoUrl = org?.logoUrl?.toString() const recordCount = org?.recordCount const userList = allUsers.filter( - (user) => user.organization === orgName + (user) => user.organisation === orgName ) const userCount = userList.length return { diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts b/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts index 70e97b09b7..138e62fdeb 100644 --- a/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts +++ b/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts @@ -4,6 +4,7 @@ import { DebugElement, EventEmitter, Input, + NO_ERRORS_SCHEMA, Output, } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' @@ -27,8 +28,8 @@ class OrganisationsFilterMockComponent { template: '
', }) class OrganisationPreviewMockComponent { - @Input() organisation: Organization - @Output() clickedOrganisation = new EventEmitter() + @Input() organization: Organization + @Output() clickedOrganization = new EventEmitter() } @Component({ @@ -85,6 +86,7 @@ describe('OrganisationsComponent', () => { useClass: OrganisationsServiceMock, }, ], + schemas: [NO_ERRORS_SCHEMA], }) .overrideComponent(OrganisationsComponent, { set: { changeDetection: ChangeDetectionStrategy.Default }, @@ -118,10 +120,10 @@ describe('OrganisationsComponent', () => { .map((debugElement) => debugElement.componentInstance) }) it('should pass first organisation (sorted by name-asc) to first ui preview component', () => { - expect(orgPreviewComponents[0].organisation.name).toEqual('A Data Org') + expect(orgPreviewComponents[0].organization.name).toEqual('A Data Org') }) it('should pass 6th organisation (sorted by name-asc) on page to 6th ui preview component', () => { - expect(orgPreviewComponents[5].organisation.name).toEqual('E Data Org') + expect(orgPreviewComponents[5].organization.name).toEqual('E Data Org') }) }) describe('pass params to ui pagination component', () => { @@ -152,13 +154,13 @@ describe('OrganisationsComponent', () => { expect(paginationComponentDE.componentInstance.currentPage).toEqual(2) }) it('should pass first organisation of second page (sorted by name-asc) to first ui preview component', () => { - expect(orgPreviewComponents[0].organisation.name).toEqual( + expect(orgPreviewComponents[0].organization.name).toEqual( 'é Data Org' ) }) it('should pass last organisation of second page (sorted by name-asc) to last ui preview component', () => { expect( - orgPreviewComponents[orgPreviewComponents.length - 1].organisation + orgPreviewComponents[orgPreviewComponents.length - 1].organization .name ).toEqual('J Data Org') }) @@ -193,12 +195,12 @@ describe('OrganisationsComponent', () => { expect(organisations[0]).toEqual(ORGANISATIONS_FIXTURE[5]) }) it('should pass organisation with max recordCount to first preview component', () => { - expect(orgPreviewComponents[0].organisation).toEqual( + expect(orgPreviewComponents[0].organization).toEqual( ORGANISATIONS_FIXTURE[5] ) }) it('should pass organisation with 6th highest recordCount to 6th preview component', () => { - expect(orgPreviewComponents[5].organisation).toEqual( + expect(orgPreviewComponents[5].organization).toEqual( ORGANISATIONS_FIXTURE[3] ) }) diff --git a/libs/feature/router/src/lib/default/router.service.spec.ts b/libs/feature/router/src/lib/default/router.service.spec.ts index bfae57c838..02e1736354 100644 --- a/libs/feature/router/src/lib/default/router.service.spec.ts +++ b/libs/feature/router/src/lib/default/router.service.spec.ts @@ -3,18 +3,24 @@ import { Router } from '@angular/router' import { RouterService } from './router.service' import { ROUTER_CONFIG } from './router.config' +import { ROUTER_ROUTE_ORGANIZATION } from './constants' const SearchRouteComponent = { name: 'searchRoute', } const RecordRouteComponent = { - name: 'recordhRoute', + name: 'recordRoute', +} + +const OrganizationRouteComponent = { + name: 'organizationRoute', } const routerConfigMock = { searchStateId: 'main', searchRouteComponent: SearchRouteComponent, recordRouteComponent: RecordRouteComponent, + organizationRouteComponent: OrganizationRouteComponent, } const RouterMock = { resetConfig: jest.fn(), @@ -37,10 +43,16 @@ const expectedRoutes = [ }, { component: { - name: 'recordhRoute', + name: 'recordRoute', }, path: 'dataset/:metadataUuid', }, + { + path: `${ROUTER_ROUTE_ORGANIZATION}/:name`, + component: { + name: 'organizationRoute', + }, + }, ] describe('RouterService', () => { let service: RouterService diff --git a/libs/ui/elements/src/lib/error/error.component.html b/libs/ui/elements/src/lib/error/error.component.html index 0b0c302281..e06e2a1b2f 100644 --- a/libs/ui/elements/src/lib/error/error.component.html +++ b/libs/ui/elements/src/lib/error/error.component.html @@ -8,11 +8,11 @@
face question_mark + >question_mark + question_mark + >question_mark +
search.error.couldNotReachApi
@@ -36,12 +36,11 @@
computer question_mark + >question_mark +
search.error.organizationHasNoDataset
-
computer question_mark + >question_mark +
search.error.recordNotFound
{{ error }}
+
+
+ computer + question_mark + +
+
+ search.error.organizationNotFound +
+
{{ error }}
+
diff --git a/libs/ui/elements/src/lib/error/error.component.ts b/libs/ui/elements/src/lib/error/error.component.ts index 6129f28a5f..a7b79425b0 100644 --- a/libs/ui/elements/src/lib/error/error.component.ts +++ b/libs/ui/elements/src/lib/error/error.component.ts @@ -6,6 +6,7 @@ export enum ErrorType { RECORD_NOT_FOUND, DATASET_HAS_NO_LINK, ORGANIZATION_HAS_NO_DATASET, + ORGANIZATION_NOT_FOUND, } @Component({ diff --git a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html index 932c405b23..85fb920c14 100644 --- a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html +++ b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html @@ -1,5 +1,5 @@ diff --git a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.ts b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.ts index f883b3c31b..c166ea575d 100644 --- a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.ts +++ b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input } from '@angular/core' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' @Component({ @@ -8,5 +8,26 @@ import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' changeDetection: ChangeDetectionStrategy.OnPush, }) export class RelatedRecordCardComponent { + private readonly baseClasses: string + @Input() record: CatalogRecord + @Input() extraClass = '' + + constructor() { + this.baseClasses = [ + 'w-72', + 'h-96', + 'overflow-hidden', + 'rounded-lg', + 'bg-white', + 'cursor-pointer', + 'block', + 'hover:-translate-y-2 ', + 'duration-[180ms]', + ].join(' ') + } + + get classList() { + return `${this.baseClasses} ${this.extraClass}` + } } diff --git a/libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts b/libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts index dc28d6869c..075b5aae7c 100644 --- a/libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts +++ b/libs/ui/layout/src/lib/max-lines/max-lines.component.spec.ts @@ -4,6 +4,13 @@ import { MaxLinesComponent } from './max-lines.component' import { Component, importProvidersFrom } from '@angular/core' import { TranslateModule } from '@ngx-translate/core' +// Mock implementation of ResizeObserver +class ResizeObserverMock { + observe = jest.fn() + unobserve = jest.fn() + disconnect = jest.fn() +} + @Component({ template: ` @@ -22,10 +29,12 @@ describe('MaxLinesComponent', () => { let maxLinesComponent: MaxLinesComponent beforeEach(() => { + ;(window as any).ResizeObserver = ResizeObserverMock + TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], + imports: [TranslateModule.forRoot(), MaxLinesComponent], providers: [importProvidersFrom(TranslateModule.forRoot())], - declarations: [MaxLinesComponent, TestHostComponent], + declarations: [TestHostComponent], }) fixture = TestBed.createComponent(TestHostComponent) hostComponent = fixture.componentInstance diff --git a/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts b/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts index c93ce310e2..1a811f97ab 100644 --- a/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts +++ b/libs/ui/layout/src/lib/max-lines/max-lines.component.stories.ts @@ -10,7 +10,7 @@ import { TranslateModule } from '@ngx-translate/core' import { TRANSLATE_DEFAULT_CONFIG, UtilI18nModule, -} from '../../../../../util/i18n/src' +} from '@geonetwork-ui/util/i18n' import { importProvidersFrom } from '@angular/core' export default { diff --git a/support-services/docker-entrypoint-initdb.d/dump b/support-services/docker-entrypoint-initdb.d/dump index 0a433c68ec163548b3136110c93fbcc891a0406e..f1f88acdffc6bb1d1fc7a5db1139104e15296fc2 100644 GIT binary patch delta 3287 zcmV;|3@G!+jU4Ze93fCgL`_fu4gdrQ0RaI30000d000003;+NC00#g7000hwA+;d^ zhN^%50RRAaoLy4OZrd;rJj-9Pw_Ga5PI_uDc3?Rv-~@rwB9~k!awQR=NP?s!_>=q( zz4^zwlpI(nAdriF%+3szud;x;%01R9MD!@33+O}k@sgQzJpL)(ey3pk7RyUG)|82! z8;sZTYF_4AUn_U>O5m;2I2&6ovp4j;5(s~*TD^J zxYSsQ6Xe_L`y6kTZIwq6l{IiUpK7|f#)PVG3eziZjlmX8oKl5=Qak=0sakJ>snLgcdZywu2Q+x4vS6XSpF zq1f!AzRMmuLmPP;p*sKdaNu2n7?k+bovF$5%C5ObHd0`snZP!OVh(ekJ)cj} zTW3jh(NSN{%8%}5RtUu3-rR4__dbS5(fi(B=i$PGqyuGwx!jy1+aB?KtDbzH}ayO zSMkU)(iXXh>vBZQl@W}Dl1Yy*_so&+S7TVRtBKKtAGf!|FtGn-$jJ}3SS$wq0V4|_ z42K1*0fz;w0*3{x1BV5y1cwEz1-AvP2JuFJ#s~lac$}44TW{Mq7JgQKg&+LT7PWP= zb-$*hr*?q@1|j{P#ViY>AR=Ckw-dVSV4>;o;%AS*(P_ zg)NB0HsdLxNo(YG z+ls_%#L9B#j@1A#Ut(@&Ovpxp=Q>u-B9yCNd%%)2$s(4wV9&8KDa$^NacQ|54D964 zIdpoMoTZIUUZB=YXFt5@Q<3+8k`(wWqqto(YIh9&B77k~f$T#Bs*tzX$ei2nv5aFe*@IO5uHQ(3?|K;C8i{XYG}bga8xPp0d&e ze=k9Zys=C;bGw$=Mv%EM2(y<7B&CewYmtAzT#|%DNa7RD!RUsZpesk<97pPZx)g|_ z6oeG$iDNjZSTxpKDCcmT=AXk&`*^Y*A+bq^%3@DLM!$uK>oF@7R6H3{y35TO#? zlbp!al~$p96V>gRxF){962(ZO%j!NSQ7hXYI39qNFAJpidRqbuf8ZEC|GY#pGpJs7 zAIdMTah_b^ln4-^=-tgj6j}6t*Hf(jG+sPF_YOY-?@Bl%0sqk@J5L)k=@s|XB5VY0nq1>#eH&VZd{Bsiec{Z-C;C2rfy1vj{;=8X z2O{-8-XGH_WBk2f6_4=y5^+rBX$&8*Rm``q#c_fG2loCQ?voO8Tt_)mS-+!fMq>O( zWR~GP22KGBY)<`*rKx#WJmr)f3!zG~gghCC-(Y7wM-dqDD7%KuiGk$@0h%FV7jePj zHV)2}=eZ_8vIk|20<)@rjj(eEbo9eENGN24q6`c26B{r{Z(#?tCE(1RnTj}V9ug(t zxUc@)>#M&3g1;8+22@6kiO{iV8}b^IjH}$(vIomMBlUU*uiv(jF1xv1ksvFdUR+oo-BuXWe9QHAas}j1V1R^?bzWNM?A{3OO2$ z$NJH^NXO%s7WslWRTeas@pozlRo@TDa3*+WeH)K6(s7W!U3y< zXd`q5qaA|Ld}U~NxWf!JtJH;!W z&?{9yf^T$xNKmNOpP=^*WVyN&J%e=LD(*Rpt{G$owvKVUA*!ra|7MiYo|#EE$waxG z#D*VMyxrW~Ga~dp#y=J2E0gBEOrwzIoBCRl{Fh0nBoBCHWIcCg9jIFi&r;PUmbNN- z)rr$q_99p~786Ae`AA}@y*r=)Du^yfR7HXK>dR|?u<(X9^hXzVa(Vsn$Uh9`ZIIx& z4k&G1cL|q-Jhg)XXm{$U1VO3kH;+_fZnvSy7JS*r60LYrsP0oek(?O0$5mpj7N=w+ zP$v#r1b{i-+QWwEn4?72T~!?gOsyS%+XA}jzOD))kCla%_>}5HJ&(JK&g%QPLxAqH zM0Npxb=Z{SlZ zE`#Y+<};z-tg5brHmxD3u0ltD4PF}*PHg>uytB}dUB=Mz>J=k6XMPzcMk5|{ytsv` zff4IN-K|h4o-d)4HRb`q(BoTprlKo9!-?$3o>{-t)b5lf?0GlsilkYW7i`rjVG!$v zi-dc?L(y~Vml`f?RR_LZv@<5U#&3XsYocnbo~M9p1w>-GISaAu2?%!x0Aw?aAm9ss z3954W8S=3aEL5qnezJGm0qq$oWt5@U4Lx|qckhO?cvIT~UMw;oT!#2}>%!qpT zYckj|P`WB_p_wUIQ@nkP^hSqkiocIC7#?cMY#*g#G=MLHedrufs72Beu9)mseR{3n z|4eT7x<`kqyMlTZD(*eI(?1Q281A=!NH6>;2Hd6IYTyDDCvw${Ur(qG*s3MA3j(zv zO`%M1Ei>|luVbU9Eo|t}oLAAo3pOF#jQd`17_&!0YXaKPee16`Z+`pN^xZc!`T6~A z{q=bAb^Z13F0Q}4`TM`${<^z+{Qmas^xfO0gRcM5P!TdVz>9Mi`&$xB^ac9~d`2EL&>3T35Na$aQAm~HtiJsB~ zQkkKR@dro02L`VtB~kMdqyIOP7kH^Q5LAw;#J}b3L!Pv(}YcQp&1!lCD20gQ;wFP1wSenRf7l845+rv_d910f{`Wkp3_H? z=Tm5Jrb>gUDB8c5I#cKz33*{OLBH1_fo|fc2afK|CQdkUXE#oGjz)!TSc82!EIX004NLMUl;p6EO_O&z`5)7ieYY)9DSw0XTpJJym;&6B~-u z88lsxmZvAv?t&D>lE3~x+Xyqj<0qIGS^6_T0JyHd0bU(1HCIe$heirAT?H421LyYDUr zmW~*Tb@A8?;N7C|V`%n82Zy_JICOqTv~zqUJa{^IzxNzY@P4`Iy3O&z;|lxGb8n-! zXm>+TQPnswi~63bAMxTXWHvS`=o)ozJ*mf#q?2hIg+6uRSQ+5!`SSBRM*elxbpX*n zps>TVBY$-M5P#zV;~q1e;B8%{uJvZm`tV_Hd!Sdk*o;exE;RFFT`ndrv8RZZs8V9G zoLWy#3z}4RnXrqcQb~|4`fOXZ4Y-oKVFmcIh^|jpkel4J(fwx&N6d$@FHC#la)R0~ z=W&0$A3Y=u!E?e?EH}d*%+=sfN_C!nO*E7!>DErrlpg~|CnKC%$XkwbMNyUwmYai@ V{4A1^6xqOCT_@db>96TbquW=0h6-X3|&CCya0T5x!?t6fUkcZpd<%$2hlJv4^mKV)ygza@_czDhY@)> zU#Gk|+HwtrXMC9>sONeMMbJ4)-i?xnhPM6axE$KIPo@1)+s)0chg|G z7ii<8??`Kl%l=#AjJkk$^5P+!to+SOwW1oxFajy=BW#@|Q_K<_`_5|BD8F>#e9(8` zXACQ(T=?6MWyd6)mc645)j0?JAqx^x_NOIjeAlV`mwzKs6#W1uiME`FsjLBqsjLEr zsjLHssjLKtsjLOJsjLR^Mt{c$004NLm04SF+qe>bR(^#a{LmJ)b+ha^z`>rNo6`-D zq(Lt2qD^_}9kB11A}9N6XKXUs*5Hgzm#3)IrZf`8tZB-$X>mOFE- z8i4r%b30{1))G9|u`(8-O#Rvc7Mw{IvaA7nj+IJTba9MJ%iUsNCCARO)4^mcscrHC zr8AlQ@W!h=>i{Lm@mEH1qjcJ=fKbU1#nOiNlJTcnqz}t*z1i4-m%9s{BVnLn9bOP< zbZscCcD1%kfh}(X3x7apj52|@kdSdRRu%uo&a$5rt1S&JoFK z%oN0N#^k?y{;mlMf7^2^P$$aZLvXO0QVLT8h@u39h)Y*Zv_ z@6Av(@k!<6y& zf|WeP?+e5+mFE$Bz)~^at&8Ir0}lNCJDeva=CsOkrm=p@*px(gNo1PhECNmr3bwm` z!IH#0E1n9O=GWLsCr|`NJWOw4aiZt>UV>(b*m;z*sL6vf6?v`+knBl$ zBgd?4GJovM0UP~klOz-}L}7{r`H2k}l(%UMv?JlnoQcXfO&t;i;kax5%$ z%>qXczJpjEu_6Sh5G|oq>A2gV%4nNZ0M!u1HXo&#!K;&u`8~r+1$&>y1W` zoZ;Ix@XsP5o86_>-03c(%edxbK{A|RajBEEihq??(YLGqWBbb;B$(!m0@bxTEEQ6! zq}cGA>YN&{%pLD#v@SO$#k1~GI2wZ`5(7jBSUn#wI*=(IG(t`W!=Zk3DU#vvB_&@F zr^HGMK1ejgY=T|px-91dcY{UA0BL-%Rfl)CyDf|g+C zu7A{wOeJ2|BurmH@Jzz&6$IZT9JdME@(Hb*%3q1f;E)Sh6I*U4!`2LF;$U9|DtRdk z_|(~>L2kjywW2^_lbC_!>aJLv*pNtdnO%<)%r-sh2$D9fvhf)Dd;8Zf2|M=u3}0e6 z6{=}_em<%-Ey7-&(;9Z$wLlq(s-n(u&VLqIC4(GYu&SsTB4+!RmV&{IuR9^2DSNbr zy790ROm#KVT~5PtoSiP;kgBcXIMVgDo*IA_ST9lT+DBTmrae{x*+%FNMh66=+1jw~ zc#j(@R-qd)?YhS`#|wg&m_tBnK}%R}SK99I>Ipm0vVah8&l>g#=(H*z!8ax(Xn$0z zPq6zAs$A8IUO>5T4ELOQ+YK@WUq?9F5>-{Jelyx=&#a`|Vxq!MVj~Vq-YjnB85#Nz zvm(kSDx0s-8Qsj?}4zXNejU3tKh4YR73KdmhXki;290 zT#^WS?-nS44x$YbWu7Cx{_+|uynkT~{n3V#LVV_ud71rODVEdoO6(W94+OmCzUG5UAljEe=YU?R75L8{*G(z<;8b(JfkZ_U5=t zS214}s?#QmUZd)IYTG*uH9y#R6=o%*jeDNK7z&+-;UYcq^M*^Xn)!2JxU3#SK(l7_lzY-3f)}`5IbT z<31qhM|=ZMRd(eUIF>Eh6YH0{wL7OVd)`mGAxYZi1v_nu8RWXYlIa0(U-ZoSr7jnC zngidinmZ=C!Eb?oYoZ#gp2vV}1cYL--3zhk2$=2>0Lc0>f94w_kl zHN`umq;qw+q43^g@#elQa(FP$Fd~N zwSx^4n)50;M8P(Nn|a^sT*e%b(71rsbl>{x&70r;HGcOEjedTATYWtpecgP0c!;Vm zZ~p%8x4#}9mfzpL9lv{9Ptet08Y)9ZX1=%lM;v?ec{B>+V}EZnaeRMtJaZr6BZ#MS zfBNWQuYIp?{OPf_0?#;g`f=;FJ@C7ad$;Ssq$i~f_DsSkJ6@738^g3TRtp-7Toy+Uv0JMwaevBLsv#BYsu(^8Wkp=O4kysqD$j25vq8pjRe_qoP4YHShP;+*Z{sR zV(aq-<`^NwP diff --git a/translations/de.json b/translations/de.json index 51ea7ea43e..1f7936a6b2 100644 --- a/translations/de.json +++ b/translations/de.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Nächste Seite", "pagination.page": "Seite", "pagination.pageOf": "von", @@ -345,6 +346,7 @@ "search.error.receivedError": "Ein Fehler ist aufgetreten", "search.error.recordHasnolink": "", "search.error.recordNotFound": "Der Datensatz mit der Kennung \"{ id }\" konnte nicht gefunden werden.", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "Suche Datensätze ...", "search.field.sortBy": "Sortieren nach:", diff --git a/translations/en.json b/translations/en.json index c9c3eff5dd..4ea86a9ea6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "Contact by email", "organization.datasets": "Datasets", "organization.lastPublishedDatasets": "Last published datasets", + "organization.lastPublishedDatasets.searchAllButton": "Search all", "pagination.nextPage": "Next page", "pagination.page": "page", "pagination.pageOf": "of", @@ -345,6 +346,7 @@ "search.error.receivedError": "An error was received", "search.error.recordHasnolink": "This record currently has no link yet, please come back later.", "search.error.recordNotFound": "The record with identifier \"{ id }\" could not be found.", + "search.error.organizationNotFound": "This organization could not be found.", "search.error.organizationHasNoDataset": "This organization has no dataset yet.", "search.field.any.placeholder": "Search datasets ...", "search.field.sortBy": "Sort by:", diff --git a/translations/es.json b/translations/es.json index 6905a25102..341cc8961f 100644 --- a/translations/es.json +++ b/translations/es.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -345,6 +346,7 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", diff --git a/translations/fr.json b/translations/fr.json index 90b6272333..5175e9ad24 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "Contacter par mail", "organization.datasets": "Données", "organization.lastPublishedDatasets": "Dernières données publiées", + "organization.lastPublishedDatasets.searchAllButton": "Rechercher tous", "pagination.nextPage": "Page suivante", "pagination.page": "page", "pagination.pageOf": "sur", @@ -345,6 +346,7 @@ "search.error.receivedError": "Erreur retournée", "search.error.recordHasnolink": "Ce dataset n'a pas encore de lien, réessayez plus tard s'il vous plaît.", "search.error.recordNotFound": "Cette donnée n'a pu être trouvée.", + "search.error.organizationNotFound": "L'organisation n'a pas pu être trouvée.", "search.error.organizationHasNoDataset": "Cette organisation n'a pas encore de données.", "search.field.any.placeholder": "Rechercher une donnée...", "search.field.sortBy": "Trier par :", diff --git a/translations/it.json b/translations/it.json index 88b55b64bb..bfcb2c988f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Pagina successiva", "pagination.page": "pagina", "pagination.pageOf": "di", @@ -345,6 +346,7 @@ "search.error.receivedError": "Errore ricevuto", "search.error.recordHasnolink": "", "search.error.recordNotFound": "Impossibile trovare questo dato", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "Cerca un dato...", "search.field.sortBy": "Ordina per:", diff --git a/translations/nl.json b/translations/nl.json index 837e08bd73..b7d0823e3a 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -345,6 +346,7 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", diff --git a/translations/pt.json b/translations/pt.json index d79183c173..096eef05fd 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -345,6 +346,7 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", diff --git a/translations/sk.json b/translations/sk.json index 4c601e5da3..56768147b0 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -245,6 +245,7 @@ "organization.details.mailContact": "", "organization.datasets": "", "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Ďalšia stránka", "pagination.page": "strana", "pagination.pageOf": "z", @@ -345,6 +346,7 @@ "search.error.receivedError": "Bola zaznamenaná chyba", "search.error.recordHasnolink": "", "search.error.recordNotFound": "Záznam s identifikátorom \"{ id }\" sa nepodarilo nájsť.", + "search.error.organizationNotFound": "", "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "Hľadať datasety ...", "search.field.sortBy": "Zoradiť podľa:",