Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

![Workflow status](https://github.com/geonetwork/geonetwork-ui/actions/workflows/checks.yml/badge.svg)    ![Workflow status](https://github.com/geonetwork/geonetwork-ui/actions/workflows/snyk-security.yml/badge.svg)    ![Workflow status](https://github.com/geonetwork/geonetwork-ui/actions/workflows/artifacts.yml/badge.svg)    [![Coverage Status](https://coveralls.io/repos/github/geonetwork/geonetwork-ui/badge.svg?branch=main)](https://coveralls.io/github/geonetwork/geonetwork-ui?branch=main)

# GeoNetwork UI
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '../../../styles.css';

:host {
display: block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<gn-ui-metadata-quality
[metadata]="parsedMetadata"
[smaller]="parsedSmaller"
[metadataQualityDisplay]="parsedMetadataQualityDisplay"
[popoverDisplay]="parsedPopoverDisplay"
[propsToValidate]="parsedPropsToValidate"
[forceComputeScore]="parsedForceComputeScore"
></gn-ui-metadata-quality>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
ChangeDetectionStrategy,
Component,
Input,
ViewEncapsulation,
} from '@angular/core'
import { BaseComponent, DefaultProviders } from '../base.component'
import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
import { ValidatorMapperKeys } from '@geonetwork-ui/util/shared'

@Component({
selector: 'wc-gn-metadata-quality',
templateUrl: './gn-metadata-quality.component.html',
styleUrls: ['./gn-metadata-quality.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.ShadowDom,
providers: [DefaultProviders],
standalone: false,
})
export class GnMetadataQualityComponent extends BaseComponent {
@Input() metadata: CatalogRecord | string // Accept both object and JSON string
@Input() smaller = false
@Input() metadataQualityDisplay = true
@Input() popoverDisplay = true
@Input() forceComputeScore = false

private _propsToValidate?: ValidatorMapperKeys[] | string
public parsedPropsToValidate?: ValidatorMapperKeys[]

@Input()
set propsToValidate(value: ValidatorMapperKeys[] | string | undefined) {
this._propsToValidate = value
if (!value) {
this.parsedPropsToValidate = undefined
return
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value)
this.parsedPropsToValidate = Array.isArray(parsed) ? parsed : undefined
} catch (e) {
console.warn('Failed to parse propsToValidate JSON:', e)
this.parsedPropsToValidate = undefined
}
} else if (Array.isArray(value)) {
this.parsedPropsToValidate = value
} else {
this.parsedPropsToValidate = undefined
}
}
get propsToValidate() {
return this._propsToValidate
}

get parsedMetadata(): CatalogRecord | null {
if (!this.metadata) return null
if (typeof this.metadata === 'string') {
try {
return JSON.parse(this.metadata)
} catch (e) {
console.error('Failed to parse metadata JSON:', e)
return null
}
}
return this.metadata
}

get parsedForceComputeScore(): boolean {
if (typeof this.forceComputeScore === 'boolean') return this.forceComputeScore
return this.forceComputeScore === 'true' || this.forceComputeScore === true
}

get parsedSmaller(): boolean {
if (typeof this.smaller === 'boolean') return this.smaller
return this.smaller === 'true' || this.smaller === true
}

get parsedMetadataQualityDisplay(): boolean {
if (typeof this.metadataQualityDisplay === 'boolean')
return this.metadataQualityDisplay
return (
this.metadataQualityDisplay === 'true' || this.metadataQualityDisplay === true
)
}

get parsedPopoverDisplay(): boolean {
if (typeof this.popoverDisplay === 'boolean') return this.popoverDisplay
return this.popoverDisplay === 'true' || this.popoverDisplay === true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- #region source -->
<gn-metadata-quality
primary-color="#0f4395"
secondary-color="#8bc832"
main-color="#555"
background-color="#fdfbff"
main-font="'Inter', sans-serif"
title-font="'DM Serif Display', serif"
metadata='{"uniqueIdentifier":"my-dataset-123","resourceTitleObject":{"default":"My Dataset","langfre":"Mon Ensemble de Données","langger":"Mein Datensatz"},"resourceAbstractObject":{"default":"A sample dataset","langfre":"Un ensemble de données exemple","langger":"Ein Beispieldatensatz"},"subtopics":["boundaries","elevation"],"contactsForResource":[{"role":"owner","address":{"deliveryPoint":"123 Main St","city":"Example City","postalCode":"12345","country":"FR","electronicMailAddress":"contact@example.org"}}],"keywords":[{"value":"geospatial","lang":"en"},{"value":"données","lang":"fr"}],"specifications":[],"quality":[{"pass":"yes","conformity":"conformant","resultDate":"2024-01-15","title":"ISO 19115 Profile","explanation":"Conforms to ISO 19115 specification"}],"extras":{"resourceTitleObject":{"langfre":"Mon Ensemble de Données","langger":"Mein Datensatz"},"sourcesIdentifiers":[],"sourceOfIdentifiers":[]},"kind":"dataset"}'
smaller="false"
metadata-quality-display="true"
popover-display="true"
force-compute-score="false"
></gn-metadata-quality>
<!-- #endregion source -->
9 changes: 8 additions & 1 deletion apps/webcomponents/src/app/webcomponents.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { GnFigureDatasetsComponent } from './components/gn-figure-datasets/gn-fi
import { GnMapViewerComponent } from './components/gn-map-viewer/gn-map-viewer.component'
import { GnResultsListComponent } from './components/gn-results-list/gn-results-list.component'
import { GnSearchInputComponent } from './components/gn-search-input/gn-search-input.component'
import { GnMetadataQualityComponent } from './components/gn-metadata-quality/gn-metadata-quality.component'
import { standaloneConfigurationObject } from './configuration'
import { StandaloneSearchModule } from './standalone-search.module'
import { BrowserModule } from '@angular/platform-browser'
Expand All @@ -52,9 +53,11 @@ import { RouterModule, TitleStrategy } from '@angular/router'
// eslint-disable-next-line @nx/enforce-module-boundaries
import { DATAHUB_ROUTER_PROVIDERS } from '@geonetwork-ui/apps/datahub/app.providers.ts'
import { NoopTitleStrategy } from './noop-title-strategy.service'
import { MetadataQualityComponent } from '@geonetwork-ui/ui/elements'
type WebComponentConstructor = new (...args: any[]) => BaseComponent | GnDatahubComponent | MetadataQualityComponent;

const CUSTOM_ELEMENTS: [
new (...args) => BaseComponent | GnDatahubComponent,
WebComponentConstructor,
string,
][] = [
[GnFacetsComponent, 'gn-facets'],
Expand All @@ -66,6 +69,8 @@ const CUSTOM_ELEMENTS: [
[GnMapViewerComponent, 'gn-map-viewer'],
[GnFigureDatasetsComponent, 'gn-figure-datasets'],
[GnDatasetViewMapComponent, 'gn-dataset-view-map'],
[GnMetadataQualityComponent, 'gn-metadata-quality'],
[MetadataQualityComponent, 'gn-ui-metadata-quality'],
[GnDatahubComponent, 'gn-datahub'],
]

Expand All @@ -81,6 +86,7 @@ const CUSTOM_ELEMENTS: [
GnMapViewerComponent,
GnFigureDatasetsComponent,
GnDatasetViewMapComponent,
GnMetadataQualityComponent,
],
imports: [
BrowserModule,
Expand Down Expand Up @@ -111,6 +117,7 @@ const CUSTOM_ELEMENTS: [
},
}
),
MetadataQualityComponent,
],
providers: [
importProvidersFrom(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class DcatApConverter extends BaseConverter<string> {
spatialExtents: readSpatialExtents,
keywords: readKeywords,
topics: readTopics,
subtopics: () => undefined, // specific to eCH-0271, not supported in DCAT-AP
resourceIdentifiers: () => undefined,
recordUpdated: readRecordUpdated,
recordCreated: readRecordCreated,
Expand Down Expand Up @@ -105,6 +106,7 @@ export class DcatApConverter extends BaseConverter<string> {
contactsForResource: () => undefined,
keywords: () => undefined,
topics: () => undefined,
subtopics: () => undefined,
licenses: () => undefined,
legalConstraints: () => undefined,
securityConstraints: () => undefined,
Expand Down
116 changes: 115 additions & 1 deletion libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ describe('Gn4FieldMapper', () => {
const result = mappingFn(output, source)
expect(result).toEqual({
title: 'Default title',
extras: {
resourceTitleObject: { default: 'Default title', langfre: 'French title' },
},
})
})
it('resourceAbstractObject - should return a function that correctly maps the field to fre lang', () => {
Expand All @@ -153,6 +156,27 @@ describe('Gn4FieldMapper', () => {
const result = mappingFn(output, source)
expect(result).toEqual({
abstract: 'French abstract',
extras: {
resourceAbstractObject: { default: 'Default abstract', langfre: 'French abstract' },
},
})
})
it('resourceAltTitleObject - should store alt title array in extras', () => {
const fieldName = 'resourceAltTitleObject'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
resourceAltTitleObject: [
{ langfre: 'Titre alternatif FR', langger: 'Alternativer Titel DE' },
],
}
const result = mappingFn(output, source)
expect(result).toEqual({
extras: {
resourceAltTitleObject: [
{ langfre: 'Titre alternatif FR', langger: 'Alternativer Titel DE' },
],
},
})
})
it('overview - should return a function that correctly maps the field', () => {
Expand Down Expand Up @@ -194,7 +218,43 @@ describe('Gn4FieldMapper', () => {
},
}
const result = mappingFn(output, source)
expect(result).toEqual({ status: 'completed' })
expect(result).toEqual({ status: 'completed', extras: { cl_statusObject: { key: 'completed', default: 'Finalisé', langfre: 'Finalisé', link: 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ProgressCode' } } })
})
it('cl_subTopicCategory - should return a function that maps subtopics', () => {
translateService.currentLang = 'de'
const fieldName = 'cl_subTopicCategory'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
cl_subTopicCategory: [
{ default: 'Subtopic 1', langger: 'Unterthema 1' },
{ default: 'Subtopic 2', langger: 'Unterthema 2' },
],
}
const result = mappingFn(output, source)
expect(result).toEqual({
subtopics: ['Unterthema 1', 'Unterthema 2'],
})
})
it('MD_LegalConstraintsOtherConstraintsObject - should store in extras and add to legalConstraints', () => {
translateService.currentLang = 'de'
const fieldName = 'MD_LegalConstraintsOtherConstraintsObject'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
MD_LegalConstraintsOtherConstraintsObject: [
{ default: 'CC-BY', langger: 'CC-BY' },
],
}
const result = mappingFn(output, source)
expect(result).toEqual({
legalConstraints: [{ text: 'CC-BY' }],
extras: {
MD_LegalConstraintsOtherConstraintsObject: [
{ default: 'CC-BY', langger: 'CC-BY' },
],
},
})
})
it('isHarvested - should return a function that correctly maps the field', () => {
const fieldName = 'isHarvested'
Expand All @@ -216,6 +276,60 @@ describe('Gn4FieldMapper', () => {
const result = mappingFn(output, source)
expect(result).toEqual({ extras: { edit: true } })
})
it('linkProtocol - should return a function that stores protocols in extras', () => {
const fieldName = 'linkProtocol'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
linkProtocol: ['MAP:Preview', 'OGC:WMS', 'WWW:DOWNLOAD-URL'],
}
const result = mappingFn(output, source)
expect(result).toEqual({
extras: {
linkProtocol: ['MAP:Preview', 'OGC:WMS', 'WWW:DOWNLOAD-URL'],
},
})
})
it('format - should return a function that stores formats in extras', () => {
const fieldName = 'format'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
format: ['ESRI Shapefile (SHP)', 'GeoJSON'],
}
const result = mappingFn(output, source)
expect(result).toEqual({
extras: {
format: ['ESRI Shapefile (SHP)', 'GeoJSON'],
},
})
})
it('featureTypes - should return a function that stores featureTypes in extras', () => {
const fieldName = 'featureTypes'
const mappingFn = service.getMappingFn(fieldName)
const output = {}
const source = {
featureTypes: [
{
typeName: 'Bornes parcellaires',
definition: '',
attributeTable: [{ name: 'ID', type: 'string' }],
},
],
}
const result = mappingFn(output, source)
expect(result).toEqual({
extras: {
featureTypes: [
{
typeName: 'Bornes parcellaires',
definition: '',
attributeTable: [{ name: 'ID', type: 'string' }],
},
],
},
})
})
it('languages - should return a list of languages even with unsupported ones and without defaultLang', () => {
const fieldName = 'otherLanguage'
const mappingFn = service.getMappingFn(fieldName)
Expand Down
Loading
Loading