Skip to content

Commit

Permalink
feat: feature catalog from linked record
Browse files Browse the repository at this point in the history
  • Loading branch information
AlitaBernachot committed Mar 3, 2025
1 parent 5b3dd27 commit c0ca80e
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 41 deletions.
38 changes: 38 additions & 0 deletions libs/api/metadata-converter/src/lib/gn4/gn4.converter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,44 @@ describe('Gn4Converter', () => {
})
})

describe('feature catalog (fcats)', () => {
it('sets the featureCatalogIdentifier from the link uuid', async () => {
const record = await service.readRecord({
...hit,
_source: {
...hit._source,
recordLink_fcats_uuid: 'linked-metadata-with-fcats',
},
})

expect(record.featureCatalogIdentifier).toEqual(
'linked-metadata-with-fcats'
)
})

it('sets the featureCatalogIdentifier from the fcats', async () => {
const record = await service.readRecord({
...hit,
_source: {
...hit._source,
related: {
fcats: [
{
_source: {
uuid: 'related-metadata-with-fcats',
},
},
],
},
},
})

expect(record.featureCatalogIdentifier).toEqual(
'related-metadata-with-fcats'
)
})
})

describe('full record', () => {
it('builds a complete record object', async () => {
const record = await service.readRecord(
Expand Down
31 changes: 30 additions & 1 deletion libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,36 @@ export class Gn4FieldMapper {
},
output
),

recordLink_fcats_uuid: (output, source) => {
const featureCatalogIdentifier = selectField(
source,
'recordLink_fcats_uuid'
)
return {
...output,
...(featureCatalogIdentifier && { featureCatalogIdentifier }),
} as CatalogRecord
},
related: (output, source) => {
const fcatSource = selectField(
getFirstValue(
selectField(
<SourceWithUnknownProps>selectField(source, 'related'),
'fcats'
)
),
'_source'
)
const featureCatalogIdentifier = selectField(
<SourceWithUnknownProps>fcatSource,
'uuid'
)
return {
...output,
...(!output.featureCatalogIdentifier && // If not already found by recordLink_fcats_uuid
featureCatalogIdentifier && { featureCatalogIdentifier }),
} as CatalogRecord
},
isPublishedToAll: (output, source) =>
this.addExtra(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export type MetadataObject = Partial<{
record: string
recordGroup: string
recordOwner: string
recordLink_fcats_uuid?: string
resolutionDistance: string[]
resolutionScaleDenominator: string[]
resourceAbstractObject: MultilingualField
Expand Down Expand Up @@ -202,7 +203,6 @@ export interface Gn4Record {
featured?: boolean
guestdownload?: boolean
selected?: boolean
related?: Gn4RecordRelated
}

export interface Gn4RecordRelated {
Expand Down
75 changes: 44 additions & 31 deletions libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
datasetRecordsFixture,
simpleDatasetRecordAsXmlFixture,
simpleDatasetRecordFixture,
simpleDatasetRecordWithFcatsFixture,
duplicateDatasetRecordAsXmlFixture,
} from '@geonetwork-ui/common/fixtures'
import {
Expand Down Expand Up @@ -56,7 +57,7 @@ class ElasticsearchServiceMock {
}

class SearchApiServiceMock {
search = jest.fn((bucket, relatedType, payload) => {
search(bucket, relatedType, payload) {
const body = JSON.parse(payload)
const count = body.size || 1234
const result: EsSearchResponse = {
Expand All @@ -73,7 +74,7 @@ class SearchApiServiceMock {
}
}
return of(result)
})
}
}

class RecordsApiServiceMock {
Expand All @@ -95,15 +96,6 @@ class RecordsApiServiceMock {
)
deleteRecord = jest.fn(() => of({}))
create = jest.fn(() => of('1234-5678'))
getFeatureCatalog = jest.fn((uuid) =>
of({
decodeMap: {
feature1: ['memberName', 'definition'],
feature2: ['name2', 'title2'],
feature3: ['name3', 'title3'],
},
})
)
}

class PlatformServiceInterfaceMock {
Expand Down Expand Up @@ -269,14 +261,17 @@ describe('Gn4Repository', () => {
describe('getFeatureCatalog', () => {
let catalog: DatasetFeatureCatalog
let metadata: CatalogRecord
const spySearch = jest.spyOn(SearchApiServiceMock.prototype, 'search')

describe('when extras feature catalog is defined ', () => {
beforeEach(async () => {
jest.clearAllMocks()
metadata = datasetRecordsFixture()[0]
catalog = await lastValueFrom(repository.getFeatureCatalog(metadata))
})

it('should not call the feature catalog API (extras contain the answer) ', () => {
expect(gn4RecordsApi.getFeatureCatalog).not.toHaveBeenCalled()
expect(spySearch).not.toHaveBeenCalled()
})

it('returns the feature catalog with mapped features', () => {
Expand All @@ -289,47 +284,65 @@ describe('Gn4Repository', () => {
})
})
})
/* TODO cas a refaire avec la fonction refaite par Alita
describe('when feature catalog exists, no extras defined', () => {
beforeEach(async () => {
metadata = {
...simpleDatasetRecordFixture(),
extras: {},
}
metadata = simpleDatasetRecordWithFcatsFixture()
spySearch.mockReturnValue(
of({ hits: { hits: datasetRecordsFixture(), total: { value: 0 } } })
)
catalog = await lastValueFrom(repository.getFeatureCatalog(metadata))
})

afterEach(() => {
spySearch.mockRestore()
})

it('calls the API with correct parameters', () => {
expect(gn4RecordsApi.getFeatureCatalog).toHaveBeenCalledWith(
'my-dataset-001', //uniqueid of the record
undefined
)
expect(spySearch).toHaveBeenCalledWith(
'bucket',
['fcats'],
'{"uuids":["feature-catalog-identifier"]}'
)
})

it('returns the feature catalog with mapped features', () => {
it('returns the attributes coming from the linked feature catalog', () => {
expect(catalog).toEqual({
attributes: [
{ name: 'memberName', title: 'definition' },
{ name: 'name2', title: 'title2' },
{ name: 'name3', title: 'title3' },
{ name: 'OBJECTID', title: 'Object identifier' },
{ name: 'Nom', title: 'Nom de la rue' },
{ name: 'Rue', title: '' },
],
})
})
})*/

it('prevent looping', async () => {
metadata = {
...simpleDatasetRecordWithFcatsFixture(),
...{ featureCatalogIdentifier: 'my-dataset-with-fcats' },
}
spySearch.mockReturnValue(
of({
hits: {
hits: metadata,
total: { value: 0 },
},
})
)
repository.getRecord = jest.fn().mockReturnValue(of(metadata))
catalog = await lastValueFrom(repository.getFeatureCatalog(metadata))
expect(catalog).toEqual(null)
})
})

describe('when feature catalog does not exist, nor in extras', () => {
beforeEach(async () => {
metadata = {
...simpleDatasetRecordFixture(),
extras: {},
}
;(gn4RecordsApi.getFeatureCatalog as jest.Mock).mockReturnValue(
of({ decodeMap: {} })
)
catalog = await lastValueFrom(repository.getFeatureCatalog(metadata))
})
it('returns null when no decode map is present', () => {
it('returns null', () => {
expect(catalog).toEqual(null)
})
})
Expand Down
22 changes: 14 additions & 8 deletions libs/api/repository/src/lib/gn4/gn4-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ export class Gn4Repository implements RecordsRepositoryInterface {
}

getFeatureCatalog(
record: CatalogRecord
record: CatalogRecord,
visited: Set<string> = new Set() // prevent looping
): Observable<DatasetFeatureCatalog | null> {
if (
record.extras &&
record.extras['featureTypes'] &&
record.extras['featureTypes'][0]?.attributeTable &&
Array.isArray(record.extras['featureTypes'][0].attributeTable)
Expand All @@ -157,15 +159,19 @@ export class Gn4Repository implements RecordsRepositoryInterface {
})
),
} as DatasetFeatureCatalog)
} else {
const featureCatalogIdentifier = record.extras['featureCatalogIdentifier']
if (featureCatalogIdentifier) {
return this.getRecord(<string>featureCatalogIdentifier).pipe(
switchMap((record) => this.getFeatureCatalog(record))
}

const featureCatalogIdentifier = record.featureCatalogIdentifier
if (featureCatalogIdentifier && !visited.has(featureCatalogIdentifier)) {
visited.add(featureCatalogIdentifier)
return this.getRecord(featureCatalogIdentifier).pipe(
switchMap((record) =>
record ? this.getFeatureCatalog(record, visited) : of(null)
)
}
return of(null)
)
}

return of(null)
}

getSimilarRecords(similarTo: CatalogRecord): Observable<CatalogRecord[]> {
Expand Down
1 change: 1 addition & 0 deletions libs/common/domain/src/lib/model/record/metadata.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export interface BaseRecord {
extras?: Record<string, unknown>
landingPage?: URL
updateFrequency?: UpdateFrequency
featureCatalogIdentifier?: string

// information related to the resource (dataset, service)
resourceIdentifier?: string
Expand Down
41 changes: 41 additions & 0 deletions libs/common/fixtures/src/lib/records.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,47 @@ export const simpleDatasetRecordFixture = (): DatasetRecord => ({
translations: {},
})

export const simpleDatasetRecordWithFcatsFixture = (): DatasetRecord => ({
uniqueIdentifier: 'my-dataset-with-fcats',
featureCatalogIdentifier: 'feature-catalog-identifier',
kind: 'dataset',
otherLanguages: [],
defaultLanguage: 'en',
recordUpdated: new Date('2022-02-01T14:12:00.000Z'),
resourceCreated: new Date('2022-09-01T12:18:19.000Z'),
resourceUpdated: new Date('2022-12-04T14:12:00.000Z'),
status: 'ongoing',
title: 'A very interesting dataset with a related feature catalog',
abstract: `This dataset has been established for testing purposes.`,
ownerOrganization: { name: 'MyOrganization', translations: {} },
contacts: [
{
email: 'bob@org.net',
position: 'developer',
organization: { name: 'MyOrganization', translations: {} },
role: 'point_of_contact',
firstName: 'Bob',
lastName: 'TheGreat',
},
],
contactsForResource: [],
keywords: [],
topics: ['testData'],
licenses: [],
legalConstraints: [],
securityConstraints: [],
otherConstraints: [],
lineage:
'This record was edited manually to test the feature catalog parsing',
spatialRepresentation: 'grid',
overviews: [],
spatialExtents: [],
temporalExtents: [],
onlineResources: [],
updateFrequency: { per: 'month', updatedTimes: 3 },
translations: {},
})

export const simpleDatasetRecordAsXmlFixture =
(): string => `<?xml version="1.0" encoding="UTF-8"?>
<mdb:MD_Metadata xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/2.0" xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0" xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0" xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/2.0" xmlns:mri="http://standards.iso.org/iso/19115/-3/mri/1.0" xmlns:mco="http://standards.iso.org/iso/19115/-3/mco/1.0" xmlns:gcx="http://standards.iso.org/iso/19115/-3/gcx/1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:mmi="http://standards.iso.org/iso/19115/-3/mmi/1.0" xmlns:mrd="http://standards.iso.org/iso/19115/-3/mrd/1.0" xmlns:mrl="http://standards.iso.org/iso/19115/-3/mrl/2.0">
Expand Down

0 comments on commit c0ca80e

Please sign in to comment.