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 dd96ada53e..c10c9ac278 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/home/search/search-filters/search-filters.component.html b/apps/datahub/src/app/home/search/search-filters/search-filters.component.html
index be613dda88..419eb96534 100644
--- a/apps/datahub/src/app/home/search/search-filters/search-filters.component.html
+++ b/apps/datahub/src/app/home/search/search-filters/search-filters.component.html
@@ -6,8 +6,8 @@
class="sm:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-7 sm:gap-4 sm:grid-rows-auto"
[ngClass]="
isOpen
- ? 'col-span-6 mb-7 sm:mb-0 sm:grid-cols-2'
- : 'col-span-4 sm:col-span-3 lg:col-span-4 sm:grid-cols-3'
+ ? 'col-span-6 mb-7 sm:mb-0'
+ : 'col-span-4 sm:col-span-3 lg:col-span-4'
"
>
@@ -125,7 +125,7 @@
diff --git a/apps/datahub/src/app/home/search/search-filters/search-filters.component.ts b/apps/datahub/src/app/home/search/search-filters/search-filters.component.ts
index 14ae7a3c96..508e335189 100644
--- a/apps/datahub/src/app/home/search/search-filters/search-filters.component.ts
+++ b/apps/datahub/src/app/home/search/search-filters/search-filters.component.ts
@@ -110,7 +110,7 @@ export class SearchFiltersComponent implements OnInit {
getClassForFilter(index: number) {
return (
- (this.isOpen ? 'block' : 'hidden') + ' ' + (index < 3 ? 'sm:block' : '')
+ (this.isOpen ? 'block' : 'hidden') + ' ' + (index < 2 ? 'sm:block' : '')
)
}
}
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
+
+
0
+ "
+ >
+
+
+
+
+
+
+
0;
+ else orgHasNoDataset
+ "
+ >
+
+
+
+ 1"
+ class="flex flex-row justify-center gap-[14px] p-1 mx-auto"
+ [ngClass]="paginationContainerClass"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extent/form-field-temporal-extent.component.css b/apps/datahub/src/app/organization/organization-header/organization-header.component.css
similarity index 100%
rename from libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extent/form-field-temporal-extent.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 }}
+
+
+
+
+
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/libs/feature/search/src/lib/results-table/results-table.component.css b/apps/datahub/src/app/organization/organization-page/organization-page.component.css
similarity index 100%
rename from libs/feature/search/src/lib/results-table/results-table.component.css
rename to apps/datahub/src/app/organization/organization-page/organization-page.component.css
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/record/header-record/header-record.component.html b/apps/datahub/src/app/record/header-record/header-record.component.html
index 7da801bc11..6046a273c7 100644
--- a/apps/datahub/src/app/record/header-record/header-record.component.html
+++ b/apps/datahub/src/app/record/header-record/header-record.component.html
@@ -13,7 +13,7 @@
-
+ >-->
{
await TestBed.configureTestingModule({
declarations: [RecordApisComponent],
imports: [TranslateModule.forRoot()],
+ schemas: [NO_ERRORS_SCHEMA],
providers: [
{
provide: MdViewFacade,
diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html
index efc77e05c4..f33d710821 100644
--- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html
+++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html
@@ -153,18 +153,15 @@
{
diff --git a/apps/datahub/src/app/router/datahub-router.service.ts b/apps/datahub/src/app/router/datahub-router.service.ts
index 06c0c97030..de25fe46dc 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/datahub/src/assets/img/favicon.ico b/apps/datahub/src/assets/img/favicon.ico
new file mode 100644
index 0000000000..b809743444
Binary files /dev/null and b/apps/datahub/src/assets/img/favicon.ico differ
diff --git a/apps/datahub/src/assets/img/logo-ign.png b/apps/datahub/src/assets/img/logo-ign.png
new file mode 100644
index 0000000000..77e1bc0d5f
Binary files /dev/null and b/apps/datahub/src/assets/img/logo-ign.png differ
diff --git a/apps/datahub/src/assets/img/logo-ministere-ecologie.jpg b/apps/datahub/src/assets/img/logo-ministere-ecologie.jpg
new file mode 100644
index 0000000000..b279d5c7f8
Binary files /dev/null and b/apps/datahub/src/assets/img/logo-ministere-ecologie.jpg differ
diff --git a/apps/datahub/src/assets/img/logo-ministere-transformation.jpg b/apps/datahub/src/assets/img/logo-ministere-transformation.jpg
new file mode 100644
index 0000000000..99406f884a
Binary files /dev/null and b/apps/datahub/src/assets/img/logo-ministere-transformation.jpg differ
diff --git a/apps/datahub/src/assets/img/logo-rf-cnig.jpg b/apps/datahub/src/assets/img/logo-rf-cnig.jpg
new file mode 100644
index 0000000000..aeb2fc68cf
Binary files /dev/null and b/apps/datahub/src/assets/img/logo-rf-cnig.jpg differ
diff --git a/apps/datahub/src/index.html b/apps/datahub/src/index.html
index 3b1d0309a6..7d5b326a99 100644
--- a/apps/datahub/src/index.html
+++ b/apps/datahub/src/index.html
@@ -2,7 +2,7 @@
-
Datahub
+
Catalogue | cartes.gouv.fr
@@ -19,6 +19,7 @@
-->
+
diff --git a/apps/datahub/src/styles.css b/apps/datahub/src/styles.css
index fdc787a110..5a94b79c9a 100644
--- a/apps/datahub/src/styles.css
+++ b/apps/datahub/src/styles.css
@@ -60,7 +60,8 @@ gn-ui-button > button:hover.bg-secondary {
background-color: rgba(0, 0, 145, 0.5) !important;
}
-[target='_blank']::after {
+[target='_blank']:not(.fr-footer__bottom-link, .fr-footer__content-link, edu-item-link
+ > .fr-btn)::after {
content: none;
}
@@ -72,6 +73,6 @@ h2.font-title {
color: white;
}
-a {
+a:not(.fr-footer__bottom-link, .fr-footer__content-link) {
background-image: none !important;
}
diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
index 0908f40c8e..89322e4ee5 100644
--- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
+++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
@@ -2,7 +2,7 @@ describe('dashboard', () => {
let pageOne
describe('pagination', () => {
it('should display different results on click on arrow', () => {
- cy.visit('/records/search')
+ cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
.first()
@@ -13,7 +13,7 @@ describe('dashboard', () => {
})
//TODO remove skip when dump contains more than 15 records
it.skip('should display different results on click on specific page and change url', () => {
- cy.visit('/records/search?_page=2')
+ cy.visit('/catalog/search?_page=2')
cy.get('gn-ui-pagination-buttons').find('gn-ui-button').eq(1).click()
cy.get('gn-ui-results-table')
.find('.table-row-cell')
@@ -36,8 +36,8 @@ describe('dashboard', () => {
})
describe('sorting', () => {
- it('should order the result list on click', () => {
- cy.visit('/records/search')
+ it.only('should order the result list on click', () => {
+ cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
.eq(1)
@@ -65,7 +65,7 @@ describe('dashboard', () => {
describe('checkboxes', () => {
it('should show the correct amount of selected records when they are selected', () => {
- cy.visit('/records/search')
+ cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
.get('gn-ui-checkbox')
@@ -75,7 +75,7 @@ describe('dashboard', () => {
})
it('should show nothing when none are selected', () => {
- cy.visit('/records/search')
+ cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
.get('gn-ui-checkbox')
@@ -87,7 +87,7 @@ describe('dashboard', () => {
})
it('should select all records when the "select all" checkbox is checked', () => {
- cy.visit('/records/search')
+ cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
.get('gn-ui-checkbox')
diff --git a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts
index a730895c2d..14ddf94cf9 100644
--- a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts
+++ b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts
@@ -5,8 +5,7 @@ describe('my-org', () => {
method: 'GET',
url: '/geonetwork/srv/api/userselections/0/101',
}).as('dataGetFirst')
- cy.visit(`/records/my-org`)
- cy.get('md-editor-dashboard-menu').find('a').first().click()
+ cy.visit(`/catalog/my-org`)
cy.wait('@dataGetFirst').its('response.statusCode').should('equal', 200)
})
describe('my-org display', () => {
@@ -31,8 +30,7 @@ describe('my-org', () => {
})
})
it('should access the user list page and show my-org users', () => {
- cy.visit(`/records/my-org`)
- cy.get('md-editor-dashboard-menu').find('a').first().click()
+ cy.visit(`/catalog/my-org`)
cy.get('[data-cy=link-to-users]').click()
cy.url().should('include', '/users/my-org')
cy.get('gn-ui-interactive-table .contents').should('have.length.above', 1)
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/app.routes.ts b/apps/metadata-editor/src/app/app.routes.ts
index b4f3b4e341..878df29ce9 100644
--- a/apps/metadata-editor/src/app/app.routes.ts
+++ b/apps/metadata-editor/src/app/app.routes.ts
@@ -3,17 +3,18 @@ import { DashboardPageComponent } from './dashboard/dashboard-page.component'
import { SignInPageComponent } from './sign-in/sign-in-page.component'
import { EditPageComponent } from './edit/edit-page.component'
import { EditRecordResolver } from './edit-record.resolver'
-import { MyOrgRecordsComponent } from './records/my-org-records/my-org-records.component'
import { MyRecordsComponent } from './records/my-records/my-records.component'
import { MyDraftComponent } from './records/my-draft/my-draft.component'
import { MyLibraryComponent } from './records/my-library/my-library.component'
import { SearchRecordsComponent } from './records/search-records/search-records-list.component'
import { MyOrgUsersComponent } from './my-org-users/my-org-users.component'
+import { MyOrgRecordsComponent } from './records/my-org-records/my-org-records.component'
+import { NewRecordResolver } from './new-record.resolver'
export const appRoutes: Route[] = [
- { path: '', component: DashboardPageComponent, pathMatch: 'prefix' },
+ { path: '', redirectTo: 'catalog/search', pathMatch: 'prefix' },
{
- path: 'records',
+ path: 'catalog',
component: DashboardPageComponent,
outlet: 'primary',
children: [
@@ -22,12 +23,45 @@ export const appRoutes: Route[] = [
redirectTo: 'search',
pathMatch: 'prefix',
},
+ {
+ path: 'discussion',
+ component: SearchRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'calendar',
+ component: SearchRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'contacts',
+ component: SearchRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'thesaurus',
+ component: SearchRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'search',
+ title: 'Search Records',
+ component: SearchRecordsComponent,
+ pathMatch: 'prefix',
+ },
{
path: 'my-org',
title: 'My Organisation',
component: MyOrgRecordsComponent,
- pathMatch: 'prefix',
},
+ ],
+ },
+ {
+ path: 'my-space',
+ component: DashboardPageComponent,
+ outlet: 'primary',
+ title: 'My space',
+ children: [
{
path: 'my-records',
title: 'My Records',
@@ -41,17 +75,11 @@ export const appRoutes: Route[] = [
pathMatch: 'prefix',
},
{
- path: 'my-library',
- title: 'My Library',
+ path: 'templates',
+ title: 'Templates',
component: MyLibraryComponent,
pathMatch: 'prefix',
},
- {
- path: 'search',
- title: 'Search Records',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
- },
],
},
{
@@ -68,7 +96,11 @@ export const appRoutes: Route[] = [
],
},
{ path: 'sign-in', component: SignInPageComponent },
- { path: 'create', component: EditPageComponent },
+ {
+ path: 'create',
+ component: EditPageComponent,
+ resolve: { record: NewRecordResolver },
+ },
{
path: 'edit/:uuid',
component: EditPageComponent,
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
index c723ebc1a9..ed67fe8b72 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
@@ -1,50 +1,84 @@
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
index 823b8eb3af..2e04d1d6f7 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
@@ -4,6 +4,12 @@ import { ActivatedRoute } from '@angular/router'
import { TranslateModule } from '@ngx-translate/core'
import { of } from 'rxjs'
import { DashboardMenuComponent } from './dashboard-menu.component'
+import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
+
+class RecordsRepositoryMock {
+ getAllDrafts = jest.fn().mockReturnValue(of(DATASET_RECORDS))
+}
describe('DashboardMenuComponent', () => {
let component: DashboardMenuComponent
@@ -17,6 +23,10 @@ describe('DashboardMenuComponent', () => {
provide: ActivatedRoute,
useValue: { params: of({ id: 1 }) },
},
+ {
+ provide: RecordsRepositoryInterface,
+ useClass: RecordsRepositoryMock,
+ },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents()
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
index e11b833cf8..4845040432 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
@@ -3,6 +3,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
import { MatIconModule } from '@angular/material/icon'
import { RouterModule } from '@angular/router'
import { TranslateModule } from '@ngx-translate/core'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
+import { map } from 'rxjs/operators'
+import { BadgeComponent } from '@geonetwork-ui/ui/inputs'
@Component({
selector: 'md-editor-dashboard-menu',
@@ -10,6 +13,18 @@ import { TranslateModule } from '@ngx-translate/core'
styleUrls: ['./dashboard-menu.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [CommonModule, RouterModule, MatIconModule, TranslateModule],
+ imports: [
+ CommonModule,
+ RouterModule,
+ MatIconModule,
+ TranslateModule,
+ BadgeComponent,
+ ],
})
-export class DashboardMenuComponent {}
+export class DashboardMenuComponent {
+ draftsCount$ = this.recordsRepository
+ .getAllDrafts()
+ .pipe(map((drafts) => drafts.length))
+
+ constructor(private recordsRepository: RecordsRepositoryInterface) {}
+}
diff --git a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html
index 311cd19287..7487ff5bf0 100644
--- a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html
+++ b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts
index b036c7dd07..08fcdb3314 100644
--- a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts
+++ b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts
@@ -4,6 +4,12 @@ import { of } from 'rxjs'
import { SidebarComponent } from './sidebar.component'
import { ActivatedRoute } from '@angular/router'
import { TranslateModule } from '@ngx-translate/core'
+import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
+
+class RecordsRepositoryMock {
+ getAllDrafts = jest.fn().mockReturnValue(of(DATASET_RECORDS))
+}
describe('SidebarComponent', () => {
let component: SidebarComponent
@@ -17,6 +23,10 @@ describe('SidebarComponent', () => {
provide: ActivatedRoute,
useValue: { params: of({ id: 1 }) },
},
+ {
+ provide: RecordsRepositoryInterface,
+ useClass: RecordsRepositoryMock,
+ },
],
schemas: [NO_ERRORS_SCHEMA],
})
diff --git a/apps/metadata-editor/src/app/edit-record.resolver.spec.ts b/apps/metadata-editor/src/app/edit-record.resolver.spec.ts
index 15d76f32a2..f8de1c1e1c 100644
--- a/apps/metadata-editor/src/app/edit-record.resolver.spec.ts
+++ b/apps/metadata-editor/src/app/edit-record.resolver.spec.ts
@@ -4,16 +4,18 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'
import { NotificationsService } from '@geonetwork-ui/feature/notifications'
import { of, throwError } from 'rxjs'
import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures'
-import { EditorService } from '@geonetwork-ui/feature/editor'
import { ActivatedRouteSnapshot, convertToParamMap } from '@angular/router'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import { TranslateModule } from '@ngx-translate/core'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
class NotificationsServiceMock {
showNotification = jest.fn()
}
-class EditorServiceMock {
- loadRecordByUuid = jest.fn(() => of(DATASET_RECORDS[0]))
+class RecordsRepositoryMock {
+ openRecordForEdition = jest.fn(() =>
+ of([DATASET_RECORDS[0], '
blabla', false])
+ )
}
const activatedRoute = {
@@ -22,20 +24,23 @@ const activatedRoute = {
describe('EditRecordResolver', () => {
let resolver: EditRecordResolver
- let editorService: EditorService
+ let recordsRepository: RecordsRepositoryInterface
let notificationsService: NotificationsService
- let record: CatalogRecord
+ let resolvedData: [CatalogRecord, string, boolean]
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, TranslateModule.forRoot()],
providers: [
{ provide: NotificationsService, useClass: NotificationsServiceMock },
- { provide: EditorService, useClass: EditorServiceMock },
+ {
+ provide: RecordsRepositoryInterface,
+ useClass: RecordsRepositoryMock,
+ },
],
})
resolver = TestBed.inject(EditRecordResolver)
- editorService = TestBed.inject(EditorService)
+ recordsRepository = TestBed.inject(RecordsRepositoryInterface)
notificationsService = TestBed.inject(NotificationsService)
})
@@ -45,23 +50,27 @@ describe('EditRecordResolver', () => {
describe('load record success', () => {
beforeEach(() => {
- record = undefined
- resolver.resolve(activatedRoute, null).subscribe((r) => (record = r))
+ resolvedData = undefined
+ resolver.resolve(activatedRoute).subscribe((r) => (resolvedData = r))
})
it('should load record by uuid', () => {
- expect(record).toBe(DATASET_RECORDS[0])
+ expect(resolvedData).toEqual([
+ DATASET_RECORDS[0],
+ '
blabla',
+ false,
+ ])
})
})
describe('load record failure', () => {
beforeEach(() => {
- editorService.loadRecordByUuid = () =>
+ recordsRepository.openRecordForEdition = () =>
throwError(() => new Error('oopsie'))
- record = undefined
- resolver.resolve(activatedRoute, null).subscribe((r) => (record = r))
+ resolvedData = undefined
+ resolver.resolve(activatedRoute).subscribe((r) => (resolvedData = r))
})
it('should not emit anything', () => {
- expect(record).toBeUndefined()
+ expect(resolvedData).toBeUndefined()
})
it('should show error notification', () => {
expect(notificationsService.showNotification).toHaveBeenCalledWith({
diff --git a/apps/metadata-editor/src/app/edit-record.resolver.ts b/apps/metadata-editor/src/app/edit-record.resolver.ts
index 96cfb312db..337a1bf858 100644
--- a/apps/metadata-editor/src/app/edit-record.resolver.ts
+++ b/apps/metadata-editor/src/app/edit-record.resolver.ts
@@ -1,39 +1,42 @@
import { Injectable } from '@angular/core'
-import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'
+import { ActivatedRouteSnapshot } from '@angular/router'
import { catchError, EMPTY, Observable } from 'rxjs'
-import { EditorService } from '@geonetwork-ui/feature/editor'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import { NotificationsService } from '@geonetwork-ui/feature/notifications'
import { TranslateService } from '@ngx-translate/core'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
@Injectable({
providedIn: 'root',
})
export class EditRecordResolver {
constructor(
- private editorService: EditorService,
+ private recordsRepository: RecordsRepositoryInterface,
private notificationsService: NotificationsService,
private translateService: TranslateService
) {}
resolve(
- route: ActivatedRouteSnapshot,
- state: RouterStateSnapshot
- ): Observable
{
- return this.editorService.loadRecordByUuid(route.paramMap.get('uuid')).pipe(
- catchError((error) => {
- this.notificationsService.showNotification({
- type: 'error',
- title: this.translateService.instant('editor.record.loadError.title'),
- text: `${this.translateService.instant(
- 'editor.record.loadError.body'
- )} ${error.message}`,
- closeMessage: this.translateService.instant(
- 'editor.record.loadError.closeMessage'
- ),
+ route: ActivatedRouteSnapshot
+ ): Observable<[CatalogRecord, string, boolean]> {
+ return this.recordsRepository
+ .openRecordForEdition(route.paramMap.get('uuid'))
+ .pipe(
+ catchError((error) => {
+ this.notificationsService.showNotification({
+ type: 'error',
+ title: this.translateService.instant(
+ 'editor.record.loadError.title'
+ ),
+ text: `${this.translateService.instant(
+ 'editor.record.loadError.body'
+ )} ${error.message}`,
+ closeMessage: this.translateService.instant(
+ 'editor.record.loadError.closeMessage'
+ ),
+ })
+ return EMPTY
})
- return EMPTY
- })
- )
+ )
}
}
diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
index 365dcea5af..aab1948240 100644
--- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
+++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
@@ -11,7 +11,33 @@
undo
- Save status
+
+
+ check_circle
+ editor.record.saveStatus.asDraftOnly
+
+
+ check_circle
+ editor.record.saveStatus.recordUpToDate
+
+
+ pending
+ editor.record.saveStatus.draftWithChangesPending
+
+
help
diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.spec.ts b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.spec.ts
index 77c3cb5b09..5c37c3f73f 100644
--- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.spec.ts
+++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.spec.ts
@@ -2,6 +2,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TopToolbarComponent } from './top-toolbar.component'
import { Component } from '@angular/core'
import { PublishButtonComponent } from '../publish-button/publish-button.component'
+import { BehaviorSubject } from 'rxjs'
+import { EditorFacade } from '@geonetwork-ui/feature/editor'
+import { TranslateModule } from '@ngx-translate/core'
+
+class EditorFacadeMock {
+ changedSinceSave$ = new BehaviorSubject(false)
+ alreadySavedOnce$ = new BehaviorSubject(false)
+}
@Component({
selector: 'md-editor-publish-button',
@@ -13,10 +21,17 @@ class MockPublishButtonComponent {}
describe('TopToolbarComponent', () => {
let component: TopToolbarComponent
let fixture: ComponentFixture
+ let editorFacade: EditorFacadeMock
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [TopToolbarComponent],
+ imports: [TopToolbarComponent, TranslateModule.forRoot()],
+ providers: [
+ {
+ provide: EditorFacade,
+ useClass: EditorFacadeMock,
+ },
+ ],
})
.overrideComponent(TopToolbarComponent, {
add: {
@@ -30,10 +45,47 @@ describe('TopToolbarComponent', () => {
fixture = TestBed.createComponent(TopToolbarComponent)
component = fixture.componentInstance
+ editorFacade = TestBed.inject(EditorFacade) as any
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
+
+ describe('save status', () => {
+ let saveStatus: string
+ beforeEach(() => {
+ component['saveStatus$'].subscribe((status) => {
+ saveStatus = status
+ })
+ })
+ describe('saved and not published', () => {
+ beforeEach(() => {
+ editorFacade.alreadySavedOnce$.next(false)
+ editorFacade.changedSinceSave$.next(true)
+ })
+ it('sets the correct status', () => {
+ expect(saveStatus).toBe('draft_only')
+ })
+ })
+ describe('saved, published and up to date', () => {
+ beforeEach(() => {
+ editorFacade.alreadySavedOnce$.next(true)
+ editorFacade.changedSinceSave$.next(false)
+ })
+ it('sets the correct status', () => {
+ expect(saveStatus).toBe('record_up_to_date')
+ })
+ })
+ describe('saved, published, pending changes', () => {
+ beforeEach(() => {
+ editorFacade.alreadySavedOnce$.next(true)
+ editorFacade.changedSinceSave$.next(true)
+ })
+ it('sets the correct status', () => {
+ expect(saveStatus).toBe('draft_changes_pending')
+ })
+ })
+ })
})
diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
index c7717d8342..36e6cba0e3 100644
--- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
+++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
@@ -3,6 +3,10 @@ import { CommonModule } from '@angular/common'
import { PublishButtonComponent } from '../publish-button/publish-button.component'
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'
import { MatIconModule } from '@angular/material/icon'
+import { EditorFacade } from '@geonetwork-ui/feature/editor'
+import { combineLatest, Observable } from 'rxjs'
+import { map } from 'rxjs/operators'
+import { TranslateModule } from '@ngx-translate/core'
@Component({
selector: 'md-editor-top-toolbar',
@@ -12,9 +16,35 @@ import { MatIconModule } from '@angular/material/icon'
PublishButtonComponent,
ButtonComponent,
MatIconModule,
+ TranslateModule,
],
templateUrl: './top-toolbar.component.html',
styleUrls: ['./top-toolbar.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class TopToolbarComponent {}
+export class TopToolbarComponent {
+ protected SaveStatus = [
+ 'draft_only', // => when creating a record
+ 'record_up_to_date', // => when the record was just published (ie saved on the server)
+ 'draft_changes_pending', // => when the record was modified and not yet published
+ // these are not used since the draft is saved locally in a synchronous way
+ // TODO: use these states when the draft is saved on the server
+ // 'draft_saving',
+ // 'draft_saving_failed',
+ ] as const
+
+ protected saveStatus$: Observable =
+ combineLatest([
+ this.editorFacade.alreadySavedOnce$,
+ this.editorFacade.changedSinceSave$,
+ ]).pipe(
+ map(([alreadySavedOnce, changedSinceSave]) => {
+ if (!alreadySavedOnce) {
+ return 'draft_only'
+ }
+ return changedSinceSave ? 'draft_changes_pending' : 'record_up_to_date'
+ })
+ )
+
+ constructor(private editorFacade: EditorFacade) {}
+}
diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts
index 7b2671b56b..401d9541ce 100644
--- a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts
+++ b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts
@@ -1,25 +1,34 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { EditPageComponent } from './edit-page.component'
-import { ActivatedRoute } from '@angular/router'
+import { ActivatedRoute, Router } from '@angular/router'
import { EditorFacade } from '@geonetwork-ui/feature/editor'
import { NO_ERRORS_SCHEMA } from '@angular/core'
import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures'
-import { Subject } from 'rxjs'
+import { BehaviorSubject, Subject } from 'rxjs'
import { NotificationsService } from '@geonetwork-ui/feature/notifications'
import { TranslateModule } from '@ngx-translate/core'
const getRoute = () => ({
snapshot: {
data: {
- record: DATASET_RECORDS[0],
+ record: [DATASET_RECORDS[0], 'blabla', false],
+ },
+ routeConfig: {
+ path: '/edit/:uuid',
},
},
})
+class RouterMock {
+ navigate = jest.fn()
+}
+
class EditorFacadeMock {
+ record$ = new BehaviorSubject(DATASET_RECORDS[0])
openRecord = jest.fn()
saveError$ = new Subject()
saveSuccess$ = new Subject()
+ draftSaveSuccess$ = new Subject()
}
class NotificationsServiceMock {
showNotification = jest.fn()
@@ -48,6 +57,10 @@ describe('EditPageComponent', () => {
provide: NotificationsService,
useClass: NotificationsServiceMock,
},
+ {
+ provide: Router,
+ useClass: RouterMock,
+ },
],
}).compileComponents()
@@ -55,42 +68,83 @@ describe('EditPageComponent', () => {
notificationsService = TestBed.inject(NotificationsService)
fixture = TestBed.createComponent(EditPageComponent)
component = fixture.componentInstance
- fixture.detectChanges()
})
it('should create', () => {
+ fixture.detectChanges()
expect(component).toBeTruthy()
})
describe('initial state', () => {
+ beforeEach(() => {
+ fixture.detectChanges()
+ })
it('calls openRecord', () => {
- expect(facade.openRecord).toHaveBeenCalledWith(DATASET_RECORDS[0])
+ expect(facade.openRecord).toHaveBeenCalledWith(
+ DATASET_RECORDS[0],
+ 'blabla',
+ false
+ )
})
})
- describe('publish error', () => {
- it('shows notification', () => {
- ;(facade.saveError$ as any).next('oopsie')
- expect(notificationsService.showNotification).toHaveBeenCalledWith({
- type: 'error',
- title: 'editor.record.publishError.title',
- text: 'editor.record.publishError.body oopsie',
- closeMessage: 'editor.record.publishError.closeMessage',
+ describe('notifications', () => {
+ beforeEach(() => {
+ fixture.detectChanges()
+ })
+ describe('publish error', () => {
+ it('shows notification', () => {
+ ;(facade.saveError$ as any).next('oopsie')
+ expect(notificationsService.showNotification).toHaveBeenCalledWith({
+ type: 'error',
+ title: 'editor.record.publishError.title',
+ text: 'editor.record.publishError.body oopsie',
+ closeMessage: 'editor.record.publishError.closeMessage',
+ })
+ })
+ })
+
+ describe('publish success', () => {
+ it('shows notification', () => {
+ ;(facade.saveSuccess$ as any).next()
+ expect(notificationsService.showNotification).toHaveBeenCalledWith(
+ {
+ type: 'success',
+ title: 'editor.record.publishSuccess.title',
+ text: 'editor.record.publishSuccess.body',
+ },
+ 2500
+ )
})
})
})
- describe('publish success', () => {
- it('shows notification', () => {
- ;(facade.saveSuccess$ as any).next()
- expect(notificationsService.showNotification).toHaveBeenCalledWith(
- {
- type: 'success',
- title: 'editor.record.publishSuccess.title',
- text: 'editor.record.publishSuccess.body',
- },
- 2500
- )
+ describe('new record', () => {
+ beforeEach(() => {
+ const activatedRoute = TestBed.inject(ActivatedRoute)
+ activatedRoute.snapshot.routeConfig.path = '/create'
+ fixture.detectChanges()
+ })
+ it('navigate from /create to /edit/uuid on first change', () => {
+ const router = TestBed.inject(Router)
+ const navigateSpy = jest.spyOn(router, 'navigate')
+ ;(facade.draftSaveSuccess$ as any).next()
+ expect(navigateSpy).toHaveBeenCalledWith(['edit', 'my-dataset-001'])
+ })
+ })
+
+ describe('unique identifier of the current record changes', () => {
+ beforeEach(() => {
+ fixture.detectChanges()
+ })
+ it('navigates to /edit/newUuid', () => {
+ const router = TestBed.inject(Router)
+ const navigateSpy = jest.spyOn(router, 'navigate')
+ ;(facade.record$ as any).next({
+ ...DATASET_RECORDS[0],
+ uniqueIdentifier: 'new-uuid',
+ })
+ expect(navigateSpy).toHaveBeenCalledWith(['edit', 'new-uuid'])
})
})
})
diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.ts b/apps/metadata-editor/src/app/edit/edit-page.component.ts
index 88373fc351..7327286a2c 100644
--- a/apps/metadata-editor/src/app/edit/edit-page.component.ts
+++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts
@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common'
import { Component, OnDestroy, OnInit } from '@angular/core'
-import { ActivatedRoute } from '@angular/router'
+import { ActivatedRoute, Router } from '@angular/router'
import {
EditorFacade,
RecordFormComponent,
@@ -14,7 +14,7 @@ import {
NotificationsService,
} from '@geonetwork-ui/feature/notifications'
import { TranslateService } from '@ngx-translate/core'
-import { Subscription } from 'rxjs'
+import { filter, Subscription, take } from 'rxjs'
@Component({
selector: 'md-editor-edit',
@@ -38,12 +38,18 @@ export class EditPageComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private facade: EditorFacade,
private notificationsService: NotificationsService,
- private translateService: TranslateService
+ private translateService: TranslateService,
+ private router: Router
) {}
ngOnInit(): void {
- const currentRecord = this.route.snapshot.data['record']
- this.facade.openRecord(currentRecord)
+ const [currentRecord, currentRecordSource, currentRecordAlreadySaved] =
+ this.route.snapshot.data['record']
+ this.facade.openRecord(
+ currentRecord,
+ currentRecordSource,
+ currentRecordAlreadySaved
+ )
this.subscription.add(
this.facade.saveError$.subscribe((error) => {
@@ -78,6 +84,26 @@ export class EditPageComponent implements OnInit, OnDestroy {
)
})
)
+
+ // if we're on the /create route, go to /edit/{uuid} on first change
+ if (this.route.snapshot.routeConfig?.path.includes('create')) {
+ this.facade.draftSaveSuccess$.pipe(take(1)).subscribe(() => {
+ this.router.navigate(['edit', currentRecord.uniqueIdentifier])
+ })
+ }
+
+ // if the record unique identifier changes, navigate to /edit/newUuid
+ this.facade.record$
+ .pipe(
+ filter(
+ (record) =>
+ record?.uniqueIdentifier !== currentRecord.uniqueIdentifier
+ ),
+ take(1)
+ )
+ .subscribe((savedRecord) => {
+ this.router.navigate(['edit', savedRecord.uniqueIdentifier])
+ })
}
ngOnDestroy() {
diff --git a/apps/metadata-editor/src/app/new-record.resolver.spec.ts b/apps/metadata-editor/src/app/new-record.resolver.spec.ts
new file mode 100644
index 0000000000..6f88be9b7a
--- /dev/null
+++ b/apps/metadata-editor/src/app/new-record.resolver.spec.ts
@@ -0,0 +1,39 @@
+import { TestBed } from '@angular/core/testing'
+import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
+import { NewRecordResolver } from './new-record.resolver'
+
+describe('NewRecordResolver', () => {
+ let resolver: NewRecordResolver
+ let resolvedData: [CatalogRecord, string, boolean]
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({})
+ resolver = TestBed.inject(NewRecordResolver)
+ })
+
+ it('should be created', () => {
+ expect(resolver).toBeTruthy()
+ })
+
+ describe('new record', () => {
+ beforeEach(() => {
+ resolvedData = undefined
+ resolver.resolve().subscribe((r) => (resolvedData = r))
+ })
+ it('creates a new empty record with a pregenerated id', () => {
+ expect(resolvedData).toMatchObject([
+ {
+ abstract: '',
+ kind: 'dataset',
+ recordUpdated: expect.any(Date),
+ status: 'ongoing',
+ temporalExtents: [],
+ title: expect.stringMatching(/^My new record/),
+ uniqueIdentifier: expect.stringMatching(/^TEMP-ID-/),
+ },
+ null,
+ false,
+ ])
+ })
+ })
+})
diff --git a/apps/metadata-editor/src/app/new-record.resolver.ts b/apps/metadata-editor/src/app/new-record.resolver.ts
new file mode 100644
index 0000000000..ec206eeb99
--- /dev/null
+++ b/apps/metadata-editor/src/app/new-record.resolver.ts
@@ -0,0 +1,39 @@
+import { Injectable } from '@angular/core'
+import { Observable, of } from 'rxjs'
+import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
+
+@Injectable({
+ providedIn: 'root',
+})
+export class NewRecordResolver {
+ resolve(): Observable<[CatalogRecord, string, boolean]> {
+ return of([
+ {
+ uniqueIdentifier: `TEMP-ID-${Date.now()}`,
+ title: `My new record (${new Date().toISOString()})`,
+ abstract: '',
+ ownerOrganization: {},
+ contacts: [],
+ recordUpdated: new Date(),
+ updateFrequency: 'unknown',
+ languages: [],
+ topics: [],
+ keywords: [],
+ licenses: [],
+ legalConstraints: [],
+ securityConstraints: [],
+ otherConstraints: [],
+ overviews: [],
+ contactsForResource: [],
+ kind: 'dataset',
+ status: 'ongoing',
+ lineage: '',
+ distributions: [],
+ spatialExtents: [],
+ temporalExtents: [],
+ } as CatalogRecord,
+ null,
+ false,
+ ])
+ }
+}
diff --git a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html
index 48f670e775..de8be72269 100644
--- a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html
+++ b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.html
@@ -1,2 +1,17 @@
-
-
+
+
+
+ dashboard.records.myDraft
+
+
+
+
+
+
+
diff --git a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.spec.ts b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.spec.ts
index b6f7b1ba75..4c42919839 100644
--- a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.spec.ts
+++ b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.spec.ts
@@ -1,9 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { MyDraftComponent } from './my-draft.component'
-import { SearchFacade } from '@geonetwork-ui/feature/search'
import { Component, importProvidersFrom } from '@angular/core'
import { TranslateModule } from '@ngx-translate/core'
import { RecordsListComponent } from '../records-list.component'
+import { of } from 'rxjs'
+import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
@Component({
selector: 'md-editor-records-list',
@@ -12,22 +14,21 @@ import { RecordsListComponent } from '../records-list.component'
})
export class MockRecordsListComponent {}
-class SearchFacadeMock {
- resetSearch = jest.fn()
+class RecordsRepositoryMock {
+ getAllDrafts = jest.fn().mockReturnValue(of(DATASET_RECORDS))
}
describe('MyDraftComponent', () => {
let component: MyDraftComponent
let fixture: ComponentFixture
- let searchFacade: SearchFacade
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
importProvidersFrom(TranslateModule.forRoot()),
{
- provide: SearchFacade,
- useClass: SearchFacadeMock,
+ provide: RecordsRepositoryInterface,
+ useClass: RecordsRepositoryMock,
},
],
}).overrideComponent(MyDraftComponent, {
@@ -38,7 +39,6 @@ describe('MyDraftComponent', () => {
imports: [MockRecordsListComponent],
},
})
- searchFacade = TestBed.inject(SearchFacade)
fixture = TestBed.createComponent(MyDraftComponent)
component = fixture.componentInstance
fixture.detectChanges()
@@ -48,9 +48,9 @@ describe('MyDraftComponent', () => {
expect(component).toBeTruthy()
})
- describe('filters', () => {
- it('clears filters on init', () => {
- expect(searchFacade.resetSearch).toHaveBeenCalled()
- })
+ it('gets all drafts on init', () => {
+ expect(
+ TestBed.inject(RecordsRepositoryInterface).getAllDrafts
+ ).toHaveBeenCalled()
})
})
diff --git a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.ts b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.ts
index 36c10fe8ae..cd9d312037 100644
--- a/apps/metadata-editor/src/app/records/my-draft/my-draft.component.ts
+++ b/apps/metadata-editor/src/app/records/my-draft/my-draft.component.ts
@@ -2,17 +2,44 @@ import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { TranslateModule } from '@ngx-translate/core'
import { RecordsListComponent } from '../records-list.component'
-import { SearchFacade } from '@geonetwork-ui/feature/search'
+import { ResultsTableContainerComponent } from '@geonetwork-ui/feature/search'
+import { ButtonComponent } from '@geonetwork-ui/ui/inputs'
+import { MatIconModule } from '@angular/material/icon'
+import { RecordsCountComponent } from '../records-count/records-count.component'
+import { UiElementsModule } from '@geonetwork-ui/ui/elements'
+import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
+import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
+import { Router } from '@angular/router'
+import { ResultsTableComponent } from '@geonetwork-ui/ui/search'
+import { startWith } from 'rxjs'
@Component({
selector: 'md-editor-my-my-draft',
templateUrl: './my-draft.component.html',
styleUrls: ['./my-draft.component.css'],
standalone: true,
- imports: [CommonModule, TranslateModule, RecordsListComponent],
+ imports: [
+ CommonModule,
+ TranslateModule,
+ RecordsListComponent,
+ ButtonComponent,
+ MatIconModule,
+ RecordsCountComponent,
+ ResultsTableContainerComponent,
+ UiElementsModule,
+ ResultsTableComponent,
+ ],
})
export class MyDraftComponent {
- constructor(public searchFacade: SearchFacade) {
- this.searchFacade.resetSearch()
+ records$ = this.recordsRepository.getAllDrafts().pipe(startWith([]))
+ hasDraft = () => true
+
+ constructor(
+ private router: Router,
+ public recordsRepository: RecordsRepositoryInterface
+ ) {}
+
+ editRecord(record: CatalogRecord) {
+ this.router.navigate(['/edit', record.uniqueIdentifier])
}
}
diff --git a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts b/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts
index 6fee469372..eb6e675179 100644
--- a/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts
+++ b/apps/metadata-editor/src/app/records/my-org-records/my-org-records.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy } from '@angular/core'
+import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { TranslateModule } from '@ngx-translate/core'
import { RecordsListComponent } from '../records-list.component'
@@ -7,7 +7,6 @@ import { SearchFacade } from '@geonetwork-ui/feature/search'
import { Organization } from '@geonetwork-ui/common/domain/model/record'
import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface'
import { EditorRouterService } from '../../router.service'
-import { UserModel } from '@geonetwork-ui/common/domain/model/user/user.model'
import { take } from 'rxjs'
@Component({
@@ -49,7 +48,6 @@ export class MyOrgRecordsComponent {
this.router.getDatahubSearchRoute(),
window.location.toString()
)
-
url.searchParams.append('publisher', this.orgName)
return url.toString()
}
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/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html
index cb63129efb..7b214aa574 100644
--- a/apps/metadata-editor/src/app/records/records-list.component.html
+++ b/apps/metadata-editor/src/app/records/records-list.component.html
@@ -1,4 +1,4 @@
-
+
-
@@ -43,10 +48,12 @@
{{ title }}
-
-
+
+ >
()
}
@@ -38,6 +37,13 @@ export class PaginationButtonsComponent {
@Output() newCurrentPageEvent = new EventEmitter()
}
+@Component({
+ selector: 'md-editor-records-count',
+ template: '',
+ standalone: true,
+})
+export class RecordsCountComponent {}
+
class SearchFacadeMock {
results$ = new BehaviorSubject(results)
currentPage$ = new BehaviorSubject(currentPage)
@@ -82,8 +88,9 @@ describe('RecordsListComponent', () => {
imports: [
CommonModule,
MatIconModule,
- RecordTableComponent,
+ ResultsTableContainerComponent,
PaginationButtonsComponent,
+ RecordsCountComponent,
],
},
})
@@ -102,7 +109,7 @@ describe('RecordsListComponent', () => {
let table, pagination
beforeEach(() => {
table = fixture.debugElement.query(
- By.directive(RecordTableComponent)
+ By.directive(ResultsTableContainerComponent)
).componentInstance
pagination = fixture.debugElement.query(
By.directive(PaginationButtonsComponent)
diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts
index 2eee0525ed..a9f4b9801f 100644
--- a/apps/metadata-editor/src/app/records/records-list.component.ts
+++ b/apps/metadata-editor/src/app/records/records-list.component.ts
@@ -4,7 +4,7 @@ import { MatIconModule } from '@angular/material/icon'
import { Router } from '@angular/router'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import {
- ResultsTableComponent,
+ ResultsTableContainerComponent,
SearchFacade,
SearchService,
} from '@geonetwork-ui/feature/search'
@@ -12,6 +12,7 @@ import { UiSearchModule } from '@geonetwork-ui/ui/search'
import { UiElementsModule } from '@geonetwork-ui/ui/elements'
import { TranslateModule } from '@ngx-translate/core'
import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
+import { RecordsCountComponent } from './records-count/records-count.component'
const includes = [
'uuid',
@@ -36,8 +37,9 @@ const includes = [
UiSearchModule,
UiElementsModule,
TranslateModule,
- ResultsTableComponent,
+ ResultsTableContainerComponent,
UiInputsModule,
+ RecordsCountComponent,
],
})
export class RecordsListComponent {
diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html
index 1379197fc3..3ad6cb07f9 100644
--- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html
+++ b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html
@@ -40,10 +40,10 @@