Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Datahub] Feature catalog - Load attributes description from a linked record if present #1137

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion conf/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ proxy_path = ""
[theme]
primary_color = "#c82850"
secondary_color = "#001638"
main_color = "#555" # All-purpose text color
main_color = "#555" # All-purpose text color
background_color = "#fdfbff"

# These optional parameters indicate which background should be used for the main header and the text color used
Expand Down
26 changes: 26 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,30 @@ describe('Gn4Converter', () => {
})
})

describe('feature catalog (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 Expand Up @@ -2363,6 +2387,7 @@ describe('Gn4Converter', () => {
isPublishedToAll: true,
id: '53583',
favoriteCount: 0,
featureTypes: [],
catalogUuid: 'metawal.wallonie.be',
edit: true,
},
Expand Down Expand Up @@ -2642,6 +2667,7 @@ describe('Gn4Converter', () => {
extras: {
catalogUuid: 'metawal.wallonie.be',
favoriteCount: 0,
featureTypes: [],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jahow says : is there a way to get a test to show that this takes in the correct value? e.g. by changing the mock values used as input?

id: '1215',
isOpenData: false,
isPublishedToAll: true,
Expand Down
26 changes: 26 additions & 0 deletions libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,32 @@ export class Gn4FieldMapper {
},
output
),
featureTypes: (output, source) =>
this.addExtra(
{
featureTypes: selectField(source, 'featureTypes'),
},
output
),
related: (output, source) => {
const fcatSource = selectField(
getFirstValue(
selectField(
<SourceWithUnknownProps>selectField(source, 'related'),
'fcats'
)
),
'_source'
)
const featureCatalogIdentifier = selectField(
<SourceWithUnknownProps>fcatSource,
'uuid'
)
return {
...output,
...(featureCatalogIdentifier && { featureCatalogIdentifier }),
} as CatalogRecord
},
isPublishedToAll: (output, source) =>
this.addExtra(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,7 @@ export interface Gn4Record {
guestdownload?: boolean
selected?: boolean
}

export interface Gn4RecordRelated {
fcats?: Gn4Record[]
}
109 changes: 102 additions & 7 deletions libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import {
datasetRecordsFixture,
simpleDatasetRecordAsXmlFixture,
simpleDatasetRecordFixture,
simpleDatasetRecordWithFcatsFixture,
duplicateDatasetRecordAsXmlFixture,
} from '@geonetwork-ui/common/fixtures'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import {
CatalogRecord,
DatasetFeatureCatalog,
} from '@geonetwork-ui/common/domain/model/record'
import { map } from 'rxjs/operators'
import { HttpErrorResponse } from '@angular/common/http'
import {
Expand Down Expand Up @@ -53,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 @@ -70,16 +74,16 @@ class SearchApiServiceMock {
}
}
return of(result)
})
}
}

class RecordsApiServiceMock {
getRecordAs = jest.fn((uuid) =>
of(`<gmd:MD_Metadata>
<gmd:fileIdentifier>
<gco:CharacterString>${uuid}</gco:CharacterString>
</gmd:fileIdentifier>
</gmd:MD_Metadata>`).pipe(map((xml) => ({ body: xml })))
<gmd:fileIdentifier>
<gco:CharacterString>${uuid}</gco:CharacterString>
</gmd:fileIdentifier>
</gmd:MD_Metadata>`).pipe(map((xml) => ({ body: xml })))
)
insert = jest.fn(() =>
of({
Expand Down Expand Up @@ -253,6 +257,97 @@ 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(spySearch).not.toHaveBeenCalled()
})

it('returns the feature catalog with mapped features', () => {
expect(catalog).toEqual({
attributes: [
{ name: 'OBJECTID', title: 'Object identifier' },
{ name: 'Nom', title: 'Nom de la rue' },
{ name: 'Rue', title: '' },
],
})
})
})
describe('when feature catalog exists, no extras defined', () => {
beforeEach(async () => {
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(spySearch).toHaveBeenCalledWith(
'bucket',
['fcats'],
'{"uuids":["feature-catalog-identifier"]}'
)
})

it('returns the attributes coming from the linked feature catalog', () => {
expect(catalog).toEqual({
attributes: [
{ 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: {},
}
catalog = await lastValueFrom(repository.getFeatureCatalog(metadata))
})
it('returns null', () => {
expect(catalog).toEqual(null)
})
})
})

describe('getSimilarRecords', () => {
let results: CatalogRecord[]
beforeEach(async () => {
Expand Down
41 changes: 39 additions & 2 deletions libs/api/repository/src/lib/gn4/gn4-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
Iso19139Converter,
} from '@geonetwork-ui/api/metadata-converter'
import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import {
CatalogRecord,
DatasetFeatureCatalog,
} from '@geonetwork-ui/common/domain/model/record'
import {
Aggregations,
AggregationsParams,
Expand All @@ -27,6 +30,7 @@ import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/reposit
import {
RecordsApiService,
SearchApiService,
FeatureResponseApiModel,
} from '@geonetwork-ui/data-access/gn4'
import {
combineLatest,
Expand Down Expand Up @@ -124,7 +128,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
return this.gn4SearchApi
.search(
'bucket',
null,
['fcats'],
JSON.stringify(
this.gn4SearchHelper.getMetadataByIdPayload(uniqueIdentifier)
)
Expand All @@ -137,6 +141,39 @@ export class Gn4Repository implements RecordsRepositoryInterface {
)
}

getFeatureCatalog(
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)
) {
return of({
attributes: record.extras['featureTypes'][0]?.attributeTable?.map(
(attr) => ({
name: attr.name,
title: attr.definition,
})
),
} as DatasetFeatureCatalog)
}

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)
}

getSimilarRecords(similarTo: CatalogRecord): Observable<CatalogRecord[]> {
return this.gn4SearchApi
.search(
Expand Down
13 changes: 8 additions & 5 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 Expand Up @@ -226,7 +227,9 @@ export interface DatasetRecord extends BaseRecord {
temporalExtents: Array<DatasetTemporalExtent>
spatialRepresentation?: SpatialRepresentationType
}

export type DatasetFeatureCatalog = {
attributes: Array<{ name: string; title: string }>
}
export interface ServiceEndpoint {
endpointUrl: URL
protocol: string
Expand Down Expand Up @@ -260,7 +263,7 @@ export type OnlineResource = DatasetOnlineResource | ServiceOnlineResource

export type CatalogRecord = DatasetRecord | ReuseRecord | ServiceRecord

export type CatalogRecordKeys =
| keyof DatasetRecord
| keyof ReuseRecord
| keyof ServiceRecord
export type CatalogRecordKeys = Exclude<
keyof DatasetRecord | keyof ReuseRecord | keyof ServiceRecord,
'featureCatalogIdentifier'
>
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import {
SearchParams,
SearchResults,
} from '../model/search'
import { CatalogRecord } from '../model/record'
import { CatalogRecord, DatasetFeatureCatalog } from '../model/record'

export abstract class RecordsRepositoryInterface {
abstract search(params: SearchParams): Observable<SearchResults>
abstract getMatchesCount(filters: FieldFilters): Observable<number>
abstract getRecord(uniqueIdentifier: string): Observable<CatalogRecord | null>
abstract getFeatureCatalog(
record: CatalogRecord
): Observable<DatasetFeatureCatalog | null>
abstract aggregate(params: AggregationsParams): Observable<Aggregations>
abstract getSimilarRecords(
similarTo: CatalogRecord
Expand Down
Loading
Loading