Skip to content

Commit

Permalink
Merge pull request #902 from geonetwork/me-fix-new-record-editor
Browse files Browse the repository at this point in the history
Metadata Editor: implement record creation
  • Loading branch information
jahow authored Jun 14, 2024
2 parents 656babc + 5b69983 commit f6f2f02
Show file tree
Hide file tree
Showing 21 changed files with 516 additions and 379 deletions.
7 changes: 6 additions & 1 deletion apps/metadata-editor/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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 { NewRecordResolver } from './new-record.resolver'

export const appRoutes: Route[] = [
{ path: '', component: DashboardPageComponent, pathMatch: 'prefix' },
Expand Down Expand Up @@ -68,7 +69,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,
Expand Down
94 changes: 72 additions & 22 deletions apps/metadata-editor/src/app/edit/edit-page.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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'

Expand All @@ -13,13 +13,22 @@ const getRoute = () => ({
data: {
record: [DATASET_RECORDS[0], '<xml>blabla</xml>', false],
},
routeConfig: {
path: '/edit/:uuid',
},
},
})

class RouterMock {
navigate = jest.fn()
}

class EditorFacadeMock {
record$ = new BehaviorSubject(DATASET_RECORDS[0])
openRecord = jest.fn()
saveError$ = new Subject<string>()
saveSuccess$ = new Subject()
draftSaveSuccess$ = new Subject()
}
class NotificationsServiceMock {
showNotification = jest.fn()
Expand Down Expand Up @@ -48,21 +57,28 @@ describe('EditPageComponent', () => {
provide: NotificationsService,
useClass: NotificationsServiceMock,
},
{
provide: Router,
useClass: RouterMock,
},
],
}).compileComponents()

facade = TestBed.inject(EditorFacade)
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],
Expand All @@ -72,29 +88,63 @@ describe('EditPageComponent', () => {
})
})

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'])
})
})
})
27 changes: 24 additions & 3 deletions apps/metadata-editor/src/app/edit/edit-page.component.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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',
Expand All @@ -38,7 +38,8 @@ 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 {
Expand Down Expand Up @@ -83,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() {
Expand Down
39 changes: 39 additions & 0 deletions apps/metadata-editor/src/app/new-record.resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
])
})
})
})
39 changes: 39 additions & 0 deletions apps/metadata-editor/src/app/new-record.resolver.ts
Original file line number Diff line number Diff line change
@@ -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,
])
}
}
16 changes: 13 additions & 3 deletions libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,17 @@ class RecordsApiServiceMock {
</gmd:fileIdentifier>
</gmd:MD_Metadata>`).pipe(map((xml) => ({ body: xml })))
)
insert = jest.fn(() => of({}))
insert = jest.fn(() =>
of({
metadataInfos: {
1234: [
{
uuid: '1234-5678-9012',
},
],
},
})
)
}

describe('Gn4Repository', () => {
Expand Down Expand Up @@ -354,8 +364,8 @@ describe('Gn4Repository', () => {
<gco:CharacterString>my-dataset-001</gco:CharacterString>`)
)
})
it('returns the record as serialized', () => {
expect(recordSource).toMatch(/<mdb:MD_Metadata/)
it('returns the unique identifier of the record as it was saved', () => {
expect(recordSource).toEqual('1234-5678-9012')
})
})
describe('without reference', () => {
Expand Down
15 changes: 7 additions & 8 deletions libs/api/repository/src/lib/gn4/gn4-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,13 @@ export class Gn4Repository implements RecordsRepositoryInterface {
undefined,
recordXml
)
.pipe(map(() => recordXml))
),
tap(() => {
// if saving was successful, the associated draft can be discarded
window.localStorage.removeItem(
this.getLocalStorageKeyForRecord(record.uniqueIdentifier)
)
})
.pipe(
map((response) => {
const metadataId = Object.keys(response.metadataInfos)[0]
return response.metadataInfos[metadataId][0].uuid
})
)
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export abstract class RecordsRepositoryInterface {
/**
* @param record
* @param referenceRecordSource
* @returns Observable<string> Returns the source of the record as it was serialized when saved
* @returns Observable<string> Returns the unique identifier of the record as it was when saved
*/
abstract saveRecord(
record: CatalogRecord,
Expand Down
2 changes: 2 additions & 0 deletions libs/feature/editor/src/lib/+state/editor.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export const saveRecordFailure = createAction(
'[Editor] Save record failure',
props<{ error: SaveRecordError }>()
)

export const draftSaveSuccess = createAction('[Editor] Draft save success')
Loading

0 comments on commit f6f2f02

Please sign in to comment.