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 4cac62f591..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 { @@ -16,6 +17,7 @@ import { DefaultRouterModule, ROUTE_PARAMS, ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, RouterService, } from '@geonetwork-ui/feature/router' @@ -92,6 +94,7 @@ 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' export const metaReducers: MetaReducer[] = !environment.production ? [] : [] @@ -145,6 +148,7 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] searchStateId: 'mainSearch', searchRouteComponent: SearchPageComponent, recordRouteComponent: RecordPageComponent, + organizationRouteComponent: OrganizationPageComponent, }), FeatureRecordModule, FeatureCatalogModule, @@ -217,6 +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_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 7abef2b1c8..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,15 +1,15 @@ 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' 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..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,13 +1,13 @@ -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 { 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.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/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/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.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 3abb743666..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 @@ -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', @@ -10,14 +9,9 @@ import { Organization } from '@geonetwork-ui/common/domain/model/record' changeDetection: ChangeDetectionStrategy.OnPush, }) export class OrganisationsPageComponent { - constructor( - private searchService: SearchService, - private orgsService: OrganizationsServiceInterface - ) {} + constructor(private routerFacade: RouterFacade) {} - searchByOrganisation(organisation: Organization) { - this.orgsService - .getFiltersForOrgs([organisation]) - .subscribe((filters) => this.searchService.setFilters(filters)) + onOrganizationSelection(organisation: Organization) { + this.routerFacade.goToOrganization(organisation.name) } } 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..c0d50f2d1c --- /dev/null +++ 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 new file mode 100644 index 0000000000..d08616ad65 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.html @@ -0,0 +1,157 @@ + + +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
+
+

+ 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 new file mode 100644 index 0000000000..bec7bfd76b --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts @@ -0,0 +1,295 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + DebugElement, + NO_ERRORS_SCHEMA, +} from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +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, + 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' + +let getHTMLElement: (dataTest: string) => HTMLElement | undefined + +const changeDetectorRefMock: Partial = { + markForCheck: jest.fn(), +} + +class OrganisationsServiceMock { + getFiltersForOrgs = jest.fn((orgs) => + of({ + orgs: orgs.reduce((prev, curr) => ({ ...prev, [curr.name]: true }), {}), + }) + ) + organisations$ = of(ORGANISATIONS_FIXTURE) +} + +const anOrganizationWithManyDatasets: Organization = ORGANISATIONS_FIXTURE[0] + +const oneDataset = [DATASET_RECORDS[0]] +const manyDatasets = DATASET_RECORDS.concat(DATASET_RECORDS[0]) + +const organizationIsLoading = new BehaviorSubject(false) +const totalPages = new BehaviorSubject(10) +const currentPage = new BehaviorSubject(0) +const results = new BehaviorSubject(manyDatasets) + +const desiredPageSize = 3 + +class SearchFacadeMock { + private pageSize = desiredPageSize + + 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('OrganizationDetailsComponent', () => { + let component: OrganizationDetailsComponent + let fixture: ComponentFixture + let searchFacade: SearchFacade + let debugElement: DebugElement + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [], + schemas: [NO_ERRORS_SCHEMA], + imports: [ + AsyncPipe, + NgIf, + ButtonComponent, + MatIconModule, + TranslateModule, + CarouselComponent, + BlockListComponent, + LetDirective, + LinkCardComponent, + NgForOf, + PreviousNextButtonsComponent, + UiElementsModule, + UiSearchModule, + MaxLinesComponent, + UiDatavizModule, + RouterLink, + UiWidgetsModule, + TranslateModule.forRoot(), + RouterTestingModule, + ], + providers: [ + { + provide: OrganizationsServiceInterface, + useClass: OrganisationsServiceMock, + }, + { + provide: SearchFacade, + useClass: SearchFacadeMock, + }, + { + provide: ChangeDetectorRef, + useValue: changeDetectorRefMock, + }, + ], + }) + .overrideComponent(OrganizationDetailsComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + }, + }) + .compileComponents() + + searchFacade = TestBed.inject(SearchFacade) + + 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() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('Left column', () => { + describe('Organization description', () => { + it('should contain the organization description', () => { + const organizationDescriptionHtml = getHTMLElement( + 'organizationDescription' + ) + + expect(organizationDescriptionHtml.textContent.trim()).toEqual( + anOrganizationWithManyDatasets.description?.trim() + ) + }) + }) + }) + + 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() + ) + }) + }) + + 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('Last Published datasets', () => { + describe('Previous Next buttons', () => { + it('should not be displayed if organization is loading', () => { + organizationIsLoading.next(true) + fixture.detectChanges() + + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') + + expect(orgDetailsNavBtn).toBeFalsy() + }) + + it('should not be displayed organization is loaded but has no pagination', () => { + organizationIsLoading.next(false) + totalPages.next(1) + fixture.detectChanges() + + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') + + expect(orgDetailsNavBtn).toBeFalsy() + }) + + it('should be displayed if organization is loadded and have pagination', () => { + organizationIsLoading.next(false) + totalPages.next(10) + fixture.detectChanges() + + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') + + expect(orgDetailsNavBtn).toBeTruthy() + }) + + it('should call paginate from the facade if button is clicked', () => { + const initialPageNumber = currentPage.getValue() + const nextPageNumber = initialPageNumber + 1 + + const orgDetailsNavBtn = getHTMLElement('orgDetailsNavBtn') + + const nextButton = orgDetailsNavBtn?.querySelector( + '[data-test="nextButton"]' + ) as HTMLElement + + ;(nextButton?.firstChild as HTMLElement).click() + fixture.detectChanges() + + expect(searchFacade.paginate).toHaveBeenCalledWith(nextPageNumber) + + const previousButton = orgDetailsNavBtn?.querySelector( + '[data-test="previousButton"]' + ) as HTMLElement + + ;(previousButton?.firstChild as HTMLElement).click() + fixture.detectChanges() + + expect(searchFacade.paginate).toHaveBeenCalledWith(initialPageNumber) + }) + + describe('Search all button', () => { + it('should send to the search page filtered on the correct organization', () => { + const orgDetailsSearchAllBtn = getHTMLElement( + 'orgDetailsSearchAllBtn' + ) + + expect(orgDetailsSearchAllBtn).toBeTruthy() + + expect(orgDetailsSearchAllBtn?.getAttribute('href')).toEqual( + `/${ROUTER_ROUTE_SEARCH}?publisher=${encodeURIComponent( + anOrganizationWithManyDatasets.name + )}` + ) + }) + }) + }) + + describe('Last published datasets', () => { + it('should display the datasets properly', () => { + const orgPageLasPubDat = getHTMLElement('orgPageLasPubDat') + + expect(orgPageLasPubDat).toBeTruthy() + expect(orgPageLasPubDat?.children.length).toEqual(desiredPageSize) + + results.next(oneDataset) + fixture.detectChanges() + + 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 new file mode 100644 index 0000000000..2656219bee --- /dev/null +++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts @@ -0,0 +1,193 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild, +} from '@angular/core' +import { AsyncPipe, NgClass, 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 { + 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' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' + +@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, + CarouselComponent, + BlockListComponent, + LetDirective, + LinkCardComponent, + NgForOf, + PreviousNextButtonsComponent, + UiElementsModule, + UiSearchModule, + MaxLinesComponent, + UiDatavizModule, + RouterLink, + UiWidgetsModule, + NgClass, + ], +}) +export class OrganizationDetailsComponent + implements OnInit, AfterViewInit, OnDestroy, OnChanges +{ + protected readonly Error = Error + protected readonly ErrorType = ErrorType + protected readonly ROUTER_ROUTE_SEARCH = ROUTER_ROUTE_SEARCH + + protected get pages() { + return new Array(this.totalPages).fill(0).map((_, i) => i + 1) + } + + lastPublishedDatasets$: Observable = of([]) + + subscriptions$: Subscription = new Subscription() + + 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( + private changeDetector: ChangeDetectorRef, + private searchFacade: SearchFacade, + private organizationsService: OrganizationsServiceInterface + ) {} + + ngOnInit(): void { + this.searchFacade.setPageSize(3) + + 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() + } + + 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) + } + } + + goToPage(page: number) { + this.searchFacade.paginate(page) + } + + private manageSubscriptions() { + this.subscriptions$.add( + 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 errorTypes = ErrorType +} diff --git a/libs/ui/elements/src/lib/max-lines/max-lines.component.css b/apps/datahub/src/app/organization/organization-header/organization-header.component.css similarity index 100% rename from libs/ui/elements/src/lib/max-lines/max-lines.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/organization-header/organization-header.component.spec.ts b/apps/datahub/src/app/organization/organization-header/organization-header.component.spec.ts new file mode 100644 index 0000000000..2ca8215648 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-header/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/organization-header/organization-header.component.ts b/apps/datahub/src/app/organization/organization-header/organization-header.component.ts new file mode 100644 index 0000000000..8e62fd986d --- /dev/null +++ b/apps/datahub/src/app/organization/organization-header/organization-header.component.ts @@ -0,0 +1,46 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { getGlobalConfig, getThemeConfig } from '@geonetwork-ui/util/app-config' +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, 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', + templateUrl: './organization-header.component.html', + styleUrls: ['./organization-header.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + UiInputsModule, + TranslateModule, + UiCatalogModule, + NgIf, + MatIconModule, + AsyncPipe, + UiElementsModule, + ], +}) +export class OrganizationHeaderComponent { + @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 location: Location, private router: Router) {} + + 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.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..026f5a90df --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.html @@ -0,0 +1,12 @@ +
+ + +
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..5068e8b7c4 --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.spec.ts @@ -0,0 +1,82 @@ +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +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 RouterFacadeMock { + pathParams$ = of({ name: ORGANISATIONS_FIXTURE[0].name } as Params) +} + +class OrganizationsServiceInterfaceMock { + organisations$ = of(ORGANISATIONS_FIXTURE) +} + +describe('OrganizationPageComponent', () => { + let component: OrganizationPageComponent + let fixture: ComponentFixture + let organizationsServiceInterface: OrganizationsServiceInterface + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + OrganizationPageComponent, + TranslateModule.forRoot({}), + RouterTestingModule, + EffectsModule.forRoot(), + StoreModule.forRoot({}), + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: RouterFacade, + useClass: RouterFacadeMock, + }, + { + provide: OrganizationsServiceInterface, + useClass: OrganizationsServiceInterfaceMock, + }, + ], + }) + .overrideComponent(OrganizationPageComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + imports: [], + schemas: [NO_ERRORS_SCHEMA], + }, + }) + .compileComponents() + + organizationsServiceInterface = TestBed.inject( + OrganizationsServiceInterface + ) + + fixture = TestBed.createComponent(OrganizationPageComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('#ngOnInit', () => { + beforeEach(() => { + component.ngOnInit() + }) + 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 new file mode 100644 index 0000000000..99cc91b16b --- /dev/null +++ b/apps/datahub/src/app/organization/organization-page/organization-page.component.ts @@ -0,0 +1,50 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' +import { RouterFacade } from '@geonetwork-ui/feature/router' +import { AsyncPipe, NgIf } from '@angular/common' +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' +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', + templateUrl: './organization-page.component.html', + styleUrls: ['./organization-page.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + AsyncPipe, + OrganizationHeaderComponent, + OrganizationDetailsComponent, + LetDirective, + NgIf, + FeatureSearchModule, + ], +}) +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]) => { + const organization = organizations.find( + (organization) => organization.name === pathParams['name'] + ) + return of(organization) + }) + ) + } +} 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.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/app/router/datahub-router.service.ts b/apps/datahub/src/app/router/datahub-router.service.ts index 22f50097b2..c8075389c6 100644 --- a/apps/datahub/src/app/router/datahub-router.service.ts +++ b/apps/datahub/src/app/router/datahub-router.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core' import { Router, Routes } from '@angular/router' import { ROUTER_ROUTE_DATASET, + ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_SEARCH, } from '@geonetwork-ui/feature/router' import { HomePageComponent } from '../home/home-page/home-page.component' @@ -12,8 +13,9 @@ 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' @Injectable({ providedIn: 'root', @@ -59,7 +61,7 @@ export class DatahubRouterService { }, }, { - path: ROUTER_ROUTE_ORGANISATIONS, + path: ROUTER_ROUTE_ORGANIZATIONS, component: OrganisationsPageComponent, data: { shouldDetach: true, @@ -71,6 +73,13 @@ export class DatahubRouterService { path: `${ROUTER_ROUTE_DATASET}/:metadataUuid`, component: RecordPageComponent, }, + { + path: `${ROUTER_ROUTE_ORGANIZATION}/:name`, + component: OrganizationPageComponent, + data: { + shouldDetach: true, + }, + }, { path: '**', redirectTo: '', pathMatch: 'full' }, ] } @@ -78,4 +87,8 @@ export class DatahubRouterService { getSearchRoute(): string { return `${ROUTER_ROUTE_HOME}/${ROUTER_ROUTE_SEARCH}` } + + getOrganizationPageRoute(): string { + return ROUTER_ROUTE_ORGANIZATION + } } 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/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.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-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.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/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/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/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/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/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/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/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/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/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/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/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..91deb6fdb8 --- /dev/null +++ b/libs/feature/catalog/src/lib/organization-url.token.ts @@ -0,0 +1,6 @@ +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.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/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/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) => { 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..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 @@ -1,14 +1,14 @@
@@ -18,22 +18,24 @@ 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 + 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 0cc798faab..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 @@ -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) } } 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/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/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.html b/libs/ui/elements/src/lib/error/error.component.html index b145a4caf1..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
@@ -32,6 +32,15 @@
search.error.receivedError
{{ error }}
+
+
+ computer + 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 cf0546eb62..a7b79425b0 100644 --- a/libs/ui/elements/src/lib/error/error.component.ts +++ b/libs/ui/elements/src/lib/error/error.component.ts @@ -5,6 +5,8 @@ export enum ErrorType { RECEIVED_ERROR, 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/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/layout/src/lib/max-lines/max-lines.component.css b/libs/ui/layout/src/lib/max-lines/max-lines.component.css new file mode 100644 index 0000000000..e69de29bb2 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 91% 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 index dc28d6869c..075b5aae7c 100644 --- 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 @@ -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/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 95% 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..1a811f97ab 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 @@ -14,12 +14,12 @@ import { 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()], diff --git a/support-services/docker-entrypoint-initdb.d/dump b/support-services/docker-entrypoint-initdb.d/dump index 0a433c68ec..f1f88acdff 100644 Binary files a/support-services/docker-entrypoint-initdb.d/dump and b/support-services/docker-entrypoint-initdb.d/dump differ diff --git a/translations/de.json b/translations/de.json index 822d224de6..1f7936a6b2 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", @@ -240,6 +240,12 @@ "organisations.sortBy.nameDesc": "Name Z → A", "organisations.sortBy.recordCountAsc": "Veröffentlichungen 0 → 9", "organisations.sortBy.recordCountDesc": "Veröffentlichungen 9 → 0", + "organization.header.recordCount": "{count, plural, =0{} one{} other{}}", + "organization.details.publishedDataset": "{count, plural, =0{} one{} other{}}", + "organization.details.mailContact": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Nächste Seite", "pagination.page": "Seite", "pagination.pageOf": "von", @@ -277,7 +283,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", @@ -340,6 +346,8 @@ "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:", "search.filters.clear": "Zurücksetzen", diff --git a/translations/en.json b/translations/en.json index 53e231e70e..4ea86a9ea6 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", @@ -240,6 +240,12 @@ "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", + "organization.datasets": "Datasets", + "organization.lastPublishedDatasets": "Last published datasets", + "organization.lastPublishedDatasets.searchAllButton": "Search all", "pagination.nextPage": "Next page", "pagination.page": "page", "pagination.pageOf": "of", @@ -277,7 +283,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", @@ -340,6 +346,8 @@ "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:", "search.filters.clear": "Reset", diff --git a/translations/es.json b/translations/es.json index 384aa3cba3..341cc8961f 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", @@ -240,6 +240,12 @@ "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": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -277,7 +283,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": "", @@ -340,6 +346,8 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", + "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", diff --git a/translations/fr.json b/translations/fr.json index 8dc865f38f..5175e9ad24 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", @@ -240,6 +240,12 @@ "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", + "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", @@ -277,7 +283,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é", @@ -340,6 +346,8 @@ "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 :", "search.filters.clear": "Réinitialiser", diff --git a/translations/it.json b/translations/it.json index 34ec4c3674..bfcb2c988f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -1,7 +1,7 @@ { "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.organizations": "{count, plural, =0{organizzazioni} one{organizzazione} other{organizzazioni}}", "chart.aggregation.average": "media", "chart.aggregation.count": "conteggio", "chart.aggregation.max": "massimo", @@ -240,6 +240,12 @@ "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": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Pagina successiva", "pagination.page": "pagina", "pagination.pageOf": "di", @@ -277,7 +283,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", @@ -340,6 +346,8 @@ "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:", "search.filters.clear": "Ripristina", diff --git a/translations/nl.json b/translations/nl.json index 98078f8718..b7d0823e3a 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", @@ -240,6 +240,12 @@ "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": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -277,7 +283,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": "", @@ -340,6 +346,8 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", + "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", diff --git a/translations/pt.json b/translations/pt.json index 46a680eb6e..096eef05fd 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", @@ -240,6 +240,12 @@ "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": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "", "pagination.page": "", "pagination.pageOf": "", @@ -277,7 +283,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": "", @@ -340,6 +346,8 @@ "search.error.receivedError": "", "search.error.recordHasnolink": "", "search.error.recordNotFound": "", + "search.error.organizationNotFound": "", + "search.error.organizationHasNoDataset": "", "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", diff --git a/translations/sk.json b/translations/sk.json index 02131776ed..56768147b0 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", @@ -240,6 +240,12 @@ "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": "", + "organization.datasets": "", + "organization.lastPublishedDatasets": "", + "organization.lastPublishedDatasets.searchAllButton": "", "pagination.nextPage": "Ďalšia stránka", "pagination.page": "strana", "pagination.pageOf": "z", @@ -276,8 +282,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ý", @@ -340,6 +346,8 @@ "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:", "search.filters.clear": "Obnoviť",