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 4a52f47ae2..703f036089 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 @@ -95,6 +95,7 @@

diff --git a/apps/datahub/src/app/home/search/search-filters/search-filters.component.spec.ts b/apps/datahub/src/app/home/search/search-filters/search-filters.component.spec.ts index e491a9bcc9..a4c0c9ead8 100644 --- a/apps/datahub/src/app/home/search/search-filters/search-filters.component.spec.ts +++ b/apps/datahub/src/app/home/search/search-filters/search-filters.component.spec.ts @@ -19,10 +19,7 @@ import { SearchFiltersComponent } from './search-filters.component' import { TranslateModule } from '@ngx-translate/core' import { By } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' -import { - AggregationsTypes, - FieldFilters, -} from '@geonetwork-ui/common/domain/search' +import { FieldFilters } from '@geonetwork-ui/common/domain/search' jest.mock('@geonetwork-ui/util/app-config', () => ({ getOptionalSearchConfig: () => ({ 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 03e143a3d5..47f6cb0ac1 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 @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, + Input, OnInit, QueryList, ViewChildren, @@ -25,6 +26,7 @@ export class SearchFiltersComponent implements OnInit { filters: QueryList searchConfig: { fieldName: string; title: string }[] isOpen = false + @Input() isQualitySortable = false constructor( public searchFacade: SearchFacade, diff --git a/apps/datahub/src/app/home/search/search-page/search-page.component.html b/apps/datahub/src/app/home/search/search-page/search-page.component.html index d0e6e986f4..a34de73590 100644 --- a/apps/datahub/src/app/home/search/search-page/search-page.component.html +++ b/apps/datahub/src/app/home/search/search-page/search-page.component.html @@ -1,10 +1,13 @@
- +
diff --git a/apps/datahub/src/app/home/search/search-page/search-page.component.ts b/apps/datahub/src/app/home/search/search-page/search-page.component.ts index 76fb7f142d..24f77cbfd0 100644 --- a/apps/datahub/src/app/home/search/search-page/search-page.component.ts +++ b/apps/datahub/src/app/home/search/search-page/search-page.component.ts @@ -2,6 +2,11 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' import { RouterFacade } from '@geonetwork-ui/feature/router' import { SearchFacade } from '@geonetwork-ui/feature/search' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' +import { MetadataQualityDisplay } from '@geonetwork-ui/ui/elements' +import { + MetadataQualityConfig, + getMetadataQualityConfig, +} from '@geonetwork-ui/util/app-config' @Component({ selector: 'datahub-search-page', @@ -10,6 +15,9 @@ import { CatalogRecord } from '@geonetwork-ui/common/domain/record' changeDetection: ChangeDetectionStrategy.OnPush, }) export class SearchPageComponent implements OnInit { + isQualitySortable = false + metadataQualityDisplay = {} as MetadataQualityDisplay + constructor( private searchRouter: RouterFacade, private searchFacade: SearchFacade @@ -17,6 +25,21 @@ export class SearchPageComponent implements OnInit { ngOnInit() { this.searchFacade.setResultsLayout('ROW') + + const cfg: MetadataQualityConfig = + getMetadataQualityConfig() || ({} as MetadataQualityConfig) + this.isQualitySortable = cfg.SORTABLE === true + this.metadataQualityDisplay = { + widget: cfg.ENABLED && cfg.DISPLAY_WIDGET_IN_SEARCH !== false, + title: cfg.DISPLAY_TITLE, + description: cfg.DISPLAY_DESCRIPTION, + contact: cfg.DISPLAY_CONTACT, + keywords: cfg.DISPLAY_KEYWORDS, + legalConstraints: cfg.DISPLAY_LEGAL_CONSTRAINTS, + topic: cfg.DISPLAY_TOPIC, + updateFrequency: cfg.DISPLAY_UPDATE_FREQUENCY, + organisation: cfg.DISPLAY_ORGANISATION, + } } onMetadataSelection(metadata: CatalogRecord): void { diff --git a/apps/datahub/src/app/record/record-page/record-page.component.html b/apps/datahub/src/app/record/record-page/record-page.component.html index 6454073fae..a4b8388ae1 100644 --- a/apps/datahub/src/app/record/record-page/record-page.component.html +++ b/apps/datahub/src/app/record/record-page/record-page.component.html @@ -2,5 +2,7 @@ - +
diff --git a/apps/datahub/src/app/record/record-page/record-page.component.ts b/apps/datahub/src/app/record/record-page/record-page.component.ts index 29db0c9715..955e5fd35b 100644 --- a/apps/datahub/src/app/record/record-page/record-page.component.ts +++ b/apps/datahub/src/app/record/record-page/record-page.component.ts @@ -1,5 +1,10 @@ import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core' import { MdViewFacade } from '@geonetwork-ui/feature/record' +import { MetadataQualityDisplay } from '@geonetwork-ui/ui/elements' +import { + MetadataQualityConfig, + getMetadataQualityConfig, +} from '@geonetwork-ui/util/app-config' @Component({ selector: 'datahub-record-page', @@ -8,8 +13,23 @@ import { MdViewFacade } from '@geonetwork-ui/feature/record' changeDetection: ChangeDetectionStrategy.OnPush, }) export class RecordPageComponent implements OnDestroy { + metadataQualityDisplay: MetadataQualityDisplay = {} as MetadataQualityDisplay + constructor(public mdViewFacade: MdViewFacade) { document.documentElement.classList.add('record-page-active') + const cfg: MetadataQualityConfig = + getMetadataQualityConfig() || ({} as MetadataQualityConfig) + this.metadataQualityDisplay = { + widget: cfg.ENABLED && cfg.DISPLAY_WIDGET_IN_DETAIL !== false, + title: cfg.DISPLAY_TITLE, + description: cfg.DISPLAY_DESCRIPTION, + contact: cfg.DISPLAY_CONTACT, + keywords: cfg.DISPLAY_KEYWORDS, + legalConstraints: cfg.DISPLAY_LEGAL_CONSTRAINTS, + topic: cfg.DISPLAY_TOPIC, + updateFrequency: cfg.DISPLAY_UPDATE_FREQUENCY, + organisation: cfg.DISPLAY_ORGANISATION, + } } ngOnDestroy() { document.documentElement.classList.remove('record-page-active') diff --git a/apps/webcomponents/src/assets/i18n/en.json b/apps/webcomponents/src/assets/i18n/en.json index ddc1c8a84a..4375eb8625 100644 --- a/apps/webcomponents/src/assets/i18n/en.json +++ b/apps/webcomponents/src/assets/i18n/en.json @@ -22,6 +22,7 @@ "results.sortBy.dateStamp": "Most recent", "results.sortBy.popularity": "Popularity", "results.sortBy.relevancy": "Relevancy", + "results.sortBy.qualityScore": "Quality score", "search.field.any.placeholder": "Search datasets, services and maps ...", "search.field.sortBy": "Sort by", "search.loading": "Loading ..." diff --git a/conf/default.toml b/conf/default.toml index 44903cbe7f..6dbf06558b 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -95,6 +95,36 @@ background_color = "#fdfbff" # Search presets will be advertised to the user along the main search field. + +### METADATA QUALITY SETTINGS + +# This section contains settings used for fine-tuning the metadata quality experience +[metadata-quality] +# By default the widget is not activated to enable it, just add this parameter. +# enabled = true +# If u want to use metadata quality widget this configuration is required + +# if you add an indexed field to calculate the qualityScore, the datahub search allow you to sort on this field with this parameter +# sortable = true + +# by default the widget appears in 2 locations in the search list and in the detail page +# allow you to hide the widget in detail +# display_widget_in_detail = false +# allow you to hide the widget in search list +# display_widget_in_search = false +# If you want see the widget in the two locations, don't fill theses configurations + +# By default the window popup all fields to view if they are filled or not but you can hide some +# display_title = false +# display_description = false +# display_topic = false +# display_keywords = false +# display_legal_constraints = false +# display_contact = false +# display_update_frequency = false +# display_organisation = false +# If you want see all fields, don't fill theses configurations + ### MAP SETTINGS # The map section allows to customize how maps are configured. diff --git a/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts b/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts index 428feae900..cbd1e2d35e 100644 --- a/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +++ b/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts @@ -53,6 +53,10 @@ export class Gn4FieldMapper { const landingPage = getAsUrl(this.metadataUrlService.getUrl(uuid)) return { ...output, uniqueIdentifier: uuid, landingPage } }, + qualityScore: (output, source) => ({ + ...output, + qualityScore: selectField(source, 'qualityScore'), + }), resourceTitleObject: (output, source) => ({ ...output, title: selectFallback( @@ -83,6 +87,12 @@ export class Gn4FieldMapper { ], } }, + cl_topic: (output, source) => ({ + ...output, + topic: getAsArray( + selectField(source, 'cl_topic') + ).map((topic) => selectTranslatedValue(topic, this.lang3)), + }), cl_status: (output, source) => ({ ...output, status: getStatusFromStatusCode( @@ -249,6 +259,18 @@ export class Gn4FieldMapper { ...output, ...(fieldName.endsWith('UseLimitationObject') ? { + legalConstraints: + fieldName === 'MD_LegalConstraintsUseLimitationObject' + ? [ + ...(output.legalConstraints || []), + ...selectField( + source, + fieldName + ).map((source: SourceWithUnknownProps) => + selectTranslatedValue(source, this.lang3) + ), + ] + : output.legalConstraints || [], useLimitations: [ ...(output.useLimitations || []), ...selectField(source, fieldName).map( diff --git a/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.spec.ts b/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.spec.ts index ca8015d7ed..93b1d5c6b7 100644 --- a/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.spec.ts +++ b/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.spec.ts @@ -611,6 +611,7 @@ describe('Gn4MetadataMapper', () => { landingPage: new URL( 'http://my.catalog.org/metadata/cf5048f6-5bbf-4e44-ba74-e6f429af51ea' ), + legalConstraints: ["Restriction légale d'utilisation à préciser"], licenses: [ { link: new URL( @@ -637,6 +638,7 @@ describe('Gn4MetadataMapper', () => { status: 'under_development', themes: ['Installations de suivi environnemental'], title: 'Surval - Données par paramètre', + topic: ['Océans'], uniqueIdentifier: 'cf5048f6-5bbf-4e44-ba74-e6f429af51ea', updateFrequency: { per: 'day', diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts b/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts index bff735b7d4..f3faeb88a3 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts @@ -11,7 +11,14 @@ export const ES_SOURCE_SUMMARY = [ 'linkProtocol', 'contactForResource.organisation', 'contact.organisation', + 'contact.email', 'userSavedCount', + 'updateFrequency', + 'cl_topic', + 'cl_maintenanceAndUpdateFrequency', + 'tag', + 'MD_LegalConstraintsUseLimitationObject', + 'qualityScore' ] export const ES_QUERY_STRING_FIELDS = [ diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts index 4dbe744ef7..b21a496d54 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts @@ -434,7 +434,14 @@ describe('ElasticsearchService', () => { 'linkProtocol', 'contactForResource.organisation', 'contact.organisation', + 'contact.email', 'userSavedCount', + "updateFrequency", + "cl_topic", + "cl_maintenanceAndUpdateFrequency", + "tag", + "MD_LegalConstraintsUseLimitationObject", + "qualityScore", ], query: { bool: { diff --git a/libs/common/domain/src/lib/record/metadata.model.ts b/libs/common/domain/src/lib/record/metadata.model.ts index a1b74566c4..8778094e86 100644 --- a/libs/common/domain/src/lib/record/metadata.model.ts +++ b/libs/common/domain/src/lib/record/metadata.model.ts @@ -58,10 +58,14 @@ export interface BaseRecord { keywords: Array // TODO: handle thesaurus and id accessConstraints: Array useLimitations: Array + legalConstraints?: Array licenses: Array overviews: Array extras?: Record landingPage?: URL + topic?: Array + updateFrequency?: UpdateFrequency + qualityScore?: number // to add: iso19139.topicCategory // to add: canonical url @@ -136,7 +140,6 @@ export interface DatasetRecord extends BaseRecord { kind: 'dataset' contactsForResource: Array status: RecordStatus - updateFrequency: UpdateFrequency datasetCreated?: Date datasetUpdated?: Date lineage: string // Explanation of the origin of this record (e.g: how, why)" diff --git a/libs/common/domain/src/lib/search/sort-by.model.ts b/libs/common/domain/src/lib/search/sort-by.model.ts index 86dfded722..bb860ab8e9 100644 --- a/libs/common/domain/src/lib/search/sort-by.model.ts +++ b/libs/common/domain/src/lib/search/sort-by.model.ts @@ -4,4 +4,5 @@ export const SortByEnum: Record = { CREATE_DATE: ['desc', 'createDate'], POPULARITY: ['desc', 'userSavedCount'], RELEVANCY: ['desc', '_score'], + QUALITY_SCORE: ['desc', 'qualityScore'], } diff --git a/libs/feature/record/src/lib/record-metadata/record-metadata.component.html b/libs/feature/record/src/lib/record-metadata/record-metadata.component.html index 094cb1cf2a..39a654b0fb 100644 --- a/libs/feature/record/src/lib/record-metadata/record-metadata.component.html +++ b/libs/feature/record/src/lib/record-metadata/record-metadata.component.html @@ -24,6 +24,15 @@
+
+

+ record.metadata.quality +

+ +
this.searchService.updateFilters(filters)) } + + get hasMetadataQualityWidget() { + return this.metadataQualityDisplay?.widget === true + } } diff --git a/libs/feature/search/src/lib/constants.ts b/libs/feature/search/src/lib/constants.ts index f7a1f42bec..72ff6f05ae 100644 --- a/libs/feature/search/src/lib/constants.ts +++ b/libs/feature/search/src/lib/constants.ts @@ -15,7 +15,13 @@ export const FIELDS_SUMMARY: FieldName[] = [ 'linkProtocol', 'contactForResource*.organisation*', 'contact*.organisation*', + 'contact*.email', 'userSavedCount', + 'cl_topic', + 'cl_maintenanceAndUpdateFrequency', + 'tag', + 'MD_LegalConstraintsUseLimitationObject', + 'qualityScore', ] export const FIELDS_BRIEF: FieldName[] = [ diff --git a/libs/feature/search/src/lib/results-list/results-list.container.component.html b/libs/feature/search/src/lib/results-list/results-list.container.component.html index 1677f514e6..e17fcabf85 100644 --- a/libs/feature/search/src/lib/results-list/results-list.container.component.html +++ b/libs/feature/search/src/lib/results-list/results-list.container.component.html @@ -2,6 +2,7 @@ () diff --git a/libs/feature/search/src/lib/sort-by/sort-by.component.ts b/libs/feature/search/src/lib/sort-by/sort-by.component.ts index 1446c5aed0..aec1dcf7bf 100644 --- a/libs/feature/search/src/lib/sort-by/sort-by.component.ts +++ b/libs/feature/search/src/lib/sort-by/sort-by.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core' +import { Component, Input, OnInit } from '@angular/core' import { marker } from '@biesbjerg/ngx-translate-extract-marker' import { SortByEnum, SortByField } from '@geonetwork-ui/common/domain/search' import { SearchFacade } from '../state/search.facade' @@ -9,7 +9,8 @@ import { filter, map } from 'rxjs/operators' selector: 'gn-ui-sort-by', templateUrl: './sort-by.component.html', }) -export class SortByComponent { +export class SortByComponent implements OnInit { + @Input() isQualitySortable: boolean choices = [ { label: marker('results.sortBy.relevancy'), @@ -23,6 +24,10 @@ export class SortByComponent { label: marker('results.sortBy.popularity'), value: SortByEnum.POPULARITY.join(','), }, + { + label: marker('results.sortBy.qualityScore'), + value: SortByEnum.QUALITY_SCORE.join(','), + }, ] currentSortBy$ = this.facade.sortBy$.pipe( filter((sortBy) => !!sortBy), @@ -34,6 +39,12 @@ export class SortByComponent { private searchService: SearchService ) {} + ngOnInit(): void { + if (!this.isQualitySortable) { + this.choices.pop() + } + } + changeSortBy(criteriaAsString: string) { this.searchService.setSortBy(criteriaAsString.split(',') as SortByField) } diff --git a/libs/ui/elements/src/index.ts b/libs/ui/elements/src/index.ts index 289d360ce2..91cd1d7172 100644 --- a/libs/ui/elements/src/index.ts +++ b/libs/ui/elements/src/index.ts @@ -2,6 +2,8 @@ export * from './lib/ui-elements.module' export * from './lib/metadata-info/metadata-info.component' export * from './lib/metadata-contact/metadata-contact.component' export * from './lib/metadata-catalog/metadata-catalog.component' +export * from './lib/metadata-quality/metadata-quality.component' +export * from './lib/metadata-quality-info/metadata-quality-info.component' export * from './lib/search-results-error/search-results-error.component' export * from './lib/thumbnail/thumbnail.component' export * from './lib/content-ghost/content-ghost.component' diff --git a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts index 042d43d6cb..cdb23bee65 100644 --- a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +++ b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts @@ -5,10 +5,7 @@ import { Input, Output, } from '@angular/core' -import { - DatasetDistribution, - DatasetRecord, -} from '@geonetwork-ui/common/domain/record' +import { DatasetRecord } from '@geonetwork-ui/common/domain/record' @Component({ selector: 'gn-ui-metadata-info', diff --git a/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.html b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.html new file mode 100644 index 0000000000..a9c4b9f3a4 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.html @@ -0,0 +1,4 @@ + diff --git a/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.spec.ts b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.spec.ts new file mode 100644 index 0000000000..4273e53795 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.spec.ts @@ -0,0 +1,87 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { + UtilI18nModule, +} from '@geonetwork-ui/util/i18n' +import { UtilSharedModule } from '@geonetwork-ui/util/shared' +import { TranslateModule } from '@ngx-translate/core' +import { MetadataQualityInfoComponent } from './metadata-quality-info.component' +import { By } from '@angular/platform-browser' +import { CommonModule } from '@angular/common' +import { MatIconModule } from '@angular/material/icon' + + +describe('MetadataQualityInfoComponent', () => { + let component: MetadataQualityInfoComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MetadataQualityInfoComponent], + imports: [ + UtilSharedModule, + CommonModule, + MatIconModule, + UtilI18nModule, + TranslateModule.forRoot(), + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataQualityInfoComponent) + component = fixture.componentInstance + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('title ok', () => { + component.name = 'title' + component.value = true + fixture.detectChanges() + + const iconElement = fixture.debugElement.query(By.css('mat-icon')) + expect(iconElement.nativeElement.innerHTML).toBe("check") + + const textElement = fixture.debugElement.query(By.css('.text')) + expect(textElement.nativeElement.innerHTML).toBe("record.metadata.quality.title.success") + }) + + it('title ko', () => { + component.name = 'title' + component.value = false + fixture.detectChanges() + + const iconElement = fixture.debugElement.query(By.css('mat-icon')) + expect(iconElement.nativeElement.innerHTML).toBe("warning_amber") + + const textElement = fixture.debugElement.query(By.css('.text')) + expect(textElement.nativeElement.innerHTML).toBe("record.metadata.quality.title.failed") + }) + + it('description ok', () => { + component.name = 'description' + component.value = true + fixture.detectChanges() + + const iconElement = fixture.debugElement.query(By.css('mat-icon')) + expect(iconElement.nativeElement.innerHTML).toBe("check") + + const textElement = fixture.debugElement.query(By.css('.text')) + expect(textElement.nativeElement.innerHTML).toBe("record.metadata.quality.description.success") + }) + + it('description ko', () => { + component.name = 'description' + component.value = false + fixture.detectChanges() + + const iconElement = fixture.debugElement.query(By.css('mat-icon')) + expect(iconElement.nativeElement.innerHTML).toBe("warning_amber") + + const textElement = fixture.debugElement.query(By.css('.text')) + expect(textElement.nativeElement.innerHTML).toBe("record.metadata.quality.description.failed") + }) + +}) diff --git a/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.ts b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.ts new file mode 100644 index 0000000000..60fa247af4 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' + +export interface MetadataQualityInfo { + name: string + value: boolean +} + +@Component({ + selector: 'gn-ui-metadata-quality-info', + templateUrl: './metadata-quality-info.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MetadataQualityInfoComponent implements MetadataQualityInfo { + @Input() name: string + @Input() value: boolean + + get icon() { + return this.value ? 'check' : 'warning_amber' + } + + get labelKey() { + return `record.metadata.quality.${this.name}.${ + this.value ? 'success' : 'failed' + }` + } +} diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css new file mode 100644 index 0000000000..b0e65500ff --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css @@ -0,0 +1,14 @@ +:host gn-ui-progress-bar { + --progress-bar-font-weight: 'normal'; +} + +.menu { + position: absolute; + z-index: 1; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.35); + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; + padding: 20px; + white-space: nowrap; +} diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html new file mode 100644 index 0000000000..aa0b46b497 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html @@ -0,0 +1,24 @@ + diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts new file mode 100644 index 0000000000..ad581f167e --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts @@ -0,0 +1,113 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { + MetadataQualityComponent, + MetadataQualityDisplay, +} from './metadata-quality.component' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { MatIconModule } from '@angular/material/icon' +import { CommonModule } from '@angular/common' +import { + TRANSLATE_DEFAULT_CONFIG, + UtilI18nModule, +} from '@geonetwork-ui/util/i18n' +import { TranslateModule } from '@ngx-translate/core' +import { MetadataQualityInfoComponent } from '../metadata-quality-info/metadata-quality-info.component' +import { ProgressBarComponent } from '@geonetwork-ui/ui/widgets' +import { UtilSharedModule } from '@geonetwork-ui/util/shared' +import { By } from '@angular/platform-browser' +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' +import { cold } from 'jasmine-marbles' +import { of } from 'rxjs' + +describe('MetadataQualityComponent', () => { + let component: MetadataQualityComponent + let fixture: ComponentFixture + const expectedInfo = [ + { name: 'title', value: true }, + { name: 'description', value: true }, + { name: 'topic', value: false }, + { name: 'keywords', value: true }, + { name: 'legalConstraints', value: false }, + { name: 'organisation', value: true }, + { name: 'contact', value: true }, + { name: 'updateFrequency', value: true }, + ] + + beforeEach(async () => { + await TestBed.configureTestingModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [ + MetadataQualityComponent, + MetadataQualityInfoComponent, + ProgressBarComponent, + ], + imports: [ + UtilSharedModule, + CommonModule, + MatIconModule, + UtilI18nModule, + TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataQualityComponent) + component = fixture.componentInstance + component.metadata = DATASET_RECORDS[0] + component.metadataQualityDisplay = { + widget: true, + } as MetadataQualityDisplay + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('focus should show menu / blur should hide', () => { + const progressBar = fixture.debugElement.query(By.css('gn-ui-progress-bar')) + progressBar.nativeElement.focus() + expect(component.isMenuShown).toBe(true) + progressBar.nativeElement.blur() + expect(component.isMenuShown).toBe(false) + }) + + it('mouseenter should show menu / mouseleave should hide', () => { + const metadataQuality = fixture.debugElement.query( + By.css('.metadata-quality') + ) + + const mouseEnterEvent = new Event('mouseenter') + metadataQuality.nativeElement.dispatchEvent(mouseEnterEvent) + expect(component.isMenuShown).toBe(true) + + const mouseLeaveEvent = new Event('mouseleave') + metadataQuality.nativeElement.dispatchEvent(mouseLeaveEvent) + expect(component.isMenuShown).toBe(false) + }) + + it('content', () => { + expect(component.metadata?.contacts[0]?.email).toBe('bob@org.net') + }) + + it('should populate info', () => { + const infoObservable = of(component.info) + const expected$ = cold('(a|)', { a: expectedInfo }) + expect(infoObservable).toBeObservable(expected$) + }) + + it('should display sub-components with correct inputs', () => { + const metadataInfoElements = fixture.debugElement.queryAll( + By.directive(MetadataQualityInfoComponent) + ) + const expectedInfoCount = expectedInfo.length + expect(metadataInfoElements.length).toBe(expectedInfoCount) + + for (let i = 0; i < expectedInfoCount; i++) { + const el = metadataInfoElements[i].componentInstance + expect(el.name).toBe(expectedInfo[i].name) + expect(el.value).toBe(expectedInfo[i].value) + } + }) +}) diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts new file mode 100644 index 0000000000..33f281d9c0 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts @@ -0,0 +1,49 @@ +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { + MetadataQualityComponent, + MetadataQualityDisplay, +} from './metadata-quality.component' +import { CommonModule } from '@angular/common' +import { + TRANSLATE_DEFAULT_CONFIG, + UtilI18nModule, +} from '@geonetwork-ui/util/i18n' +import { TranslateModule } from '@ngx-translate/core' +import { MetadataQualityInfoComponent } from '../metadata-quality-info/metadata-quality-info.component' +import { ProgressBarComponent } from '@geonetwork-ui/ui/widgets' +import { MatIconModule } from '@angular/material/icon' + +export default { + title: 'Elements/MetadataQualityComponent', + component: MetadataQualityComponent, + decorators: [ + moduleMetadata({ + declarations: [ProgressBarComponent, MetadataQualityInfoComponent], + imports: [ + CommonModule, + MatIconModule, + UtilI18nModule, + TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + ], + }), + ], +} as Meta + +export const Primary: StoryObj = { + args: { + smaller: false, + metadata: DATASET_RECORDS[0], + metadataQualityDisplay: { + widget: true, + title: true, + description: true, + topic: true, + keywords: true, + legalConstraints: true, + organisation: true, + contact: true, + updateFrequency: true, + } as MetadataQualityDisplay, + }, +} diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts new file mode 100644 index 0000000000..4aafdaca27 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts @@ -0,0 +1,83 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + OnInit, + SimpleChanges, +} from '@angular/core' +import { MetadataQualityInfo } from '../metadata-quality-info/metadata-quality-info.component' +import { CatalogRecord } from '@geonetwork-ui/common/domain/record' + +export interface MetadataQualityDisplay { + widget: boolean + title: boolean + description: boolean + topic: boolean + keywords: boolean + legalConstraints: boolean + organisation: boolean + contact: boolean + updateFrequency: boolean +} + +@Component({ + selector: 'gn-ui-metadata-quality', + templateUrl: './metadata-quality.component.html', + styleUrls: ['./metadata-quality.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MetadataQualityComponent implements OnChanges, OnInit { + @Input() metadata: Partial + @Input() smaller = false + @Input() metadataQualityDisplay: MetadataQualityDisplay + + info: MetadataQualityInfo[] = [] + + isMenuShown = false + + get qualityScore() { + return typeof this.metadata.qualityScore === 'number' + ? this.metadata.qualityScore + : this.calculatedQualityScore + } + + get calculatedQualityScore(): number { + return Math.round( + (this.info.filter(({ value }) => value === true).length * 100) / + this.info.length + ) + } + + showMenu() { + this.isMenuShown = true + } + + hideMenu() { + this.isMenuShown = false + } + + private add(name: string, value: boolean) { + if (this.metadataQualityDisplay?.[name] !== false) { + this.info.push({ name, value }) + } + } + + ngOnInit() { + const contact = this.metadata?.contacts?.[0] + this.info = [] + this.add('title', !!this.metadata?.title) + this.add('description', !!this.metadata?.abstract) + this.add('topic', this.metadata?.topic?.length > 0) + this.add('keywords', this.metadata?.keywords?.length > 0) + this.add('legalConstraints', this.metadata?.legalConstraints?.length > 0) + this.add('organisation', !!contact?.organization) + this.add('contact', !!contact?.email) + this.add('updateFrequency', !!this.metadata?.updateFrequency) + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['metadata'] || changes['metadataQualityDisplay']) + this.ngOnInit() + } +} diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index 6e0566fa54..0a1254bb4a 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -16,6 +16,8 @@ import { LinkCardComponent } from './link-card/link-card.component' import { RelatedRecordCardComponent } from './related-record-card/related-record-card.component' import { MetadataContactComponent } from './metadata-contact/metadata-contact.component' import { MetadataCatalogComponent } from './metadata-catalog/metadata-catalog.component' +import { MetadataQualityComponent } from './metadata-quality/metadata-quality.component' +import { MetadataQualityInfoComponent } from './metadata-quality-info/metadata-quality-info.component' import { SearchResultsErrorComponent } from './search-results-error/search-results-error.component' import { PaginationComponent } from './pagination/pagination.component' import { ThumbnailComponent } from './thumbnail/thumbnail.component' @@ -50,6 +52,8 @@ import { PaginationButtonsComponent } from './pagination-buttons/pagination-butt RelatedRecordCardComponent, MetadataContactComponent, MetadataCatalogComponent, + MetadataQualityComponent, + MetadataQualityInfoComponent, SearchResultsErrorComponent, PaginationComponent, ThumbnailComponent, @@ -68,6 +72,8 @@ import { PaginationButtonsComponent } from './pagination-buttons/pagination-butt RelatedRecordCardComponent, MetadataContactComponent, MetadataCatalogComponent, + MetadataQualityComponent, + MetadataQualityInfoComponent, SearchResultsErrorComponent, PaginationComponent, ThumbnailComponent, diff --git a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.css b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.css index e69de29bb2..da9b78dc10 100644 --- a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.css +++ b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.css @@ -0,0 +1,3 @@ +.limit-organisation-with-quality { + max-width: calc(100% - 170px); +} diff --git a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html index e5457ed705..27cafdca7e 100644 --- a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +++ b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html @@ -35,6 +35,9 @@
{{ organization?.name }}
@@ -53,8 +56,17 @@ >
+ +
+
@Input() linkHref: string = null + @Input() metadataQualityDisplay: MetadataQualityDisplay @Output() mdSelect = new EventEmitter() subscription = new Subscription() abstract: string @@ -45,6 +47,9 @@ export class RecordPreviewComponent implements OnInit, OnDestroy { get organization(): Organization { return this.record.ownerOrganization } + get hasMetadataQualityWidget(): boolean { + return this.metadataQualityDisplay?.widget === true + } constructor(protected elementRef: ElementRef) {} diff --git a/libs/ui/search/src/lib/results-list-item/results-list-item.component.ts b/libs/ui/search/src/lib/results-list-item/results-list-item.component.ts index cae5afa220..d064692235 100644 --- a/libs/ui/search/src/lib/results-list-item/results-list-item.component.ts +++ b/libs/ui/search/src/lib/results-list-item/results-list-item.component.ts @@ -14,6 +14,7 @@ import { import { RecordPreviewComponent } from '../record-preview/record-preview.component' import { ResultsLayoutConfigItem } from '../results-list/results-layout.config' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' +import { MetadataQualityDisplay } from '@geonetwork-ui/ui/elements' @Component({ selector: 'gn-ui-results-list-item', @@ -25,6 +26,7 @@ export class ResultsListItemComponent implements OnChanges, AfterViewInit { @Input() layoutConfig: ResultsLayoutConfigItem @Input() record: CatalogRecord @Input() favoriteTemplate: TemplateRef<{ $implicit: CatalogRecord }> + @Input() metadataQualityDisplay: MetadataQualityDisplay @Input() linkHref: string @Output() mdSelect = new EventEmitter() initialized = false @@ -50,6 +52,7 @@ export class ResultsListItemComponent implements OnChanges, AfterViewInit { this.cardRef.clear() const componentFactory = this.cardRef.createComponent(resolver) + componentFactory.instance.metadataQualityDisplay = this.metadataQualityDisplay componentFactory.instance.record = this.record componentFactory.instance.favoriteTemplate = this.favoriteTemplate componentFactory.instance.mdSelect.subscribe((record) => diff --git a/libs/ui/search/src/lib/results-list/results-list.component.html b/libs/ui/search/src/lib/results-list/results-list.component.html index cef3198f06..fa2bb5f4a3 100644 --- a/libs/ui/search/src/lib/results-list/results-list.component.html +++ b/libs/ui/search/src/lib/results-list/results-list.component.html @@ -8,6 +8,7 @@ @Input() recordUrlGetter: (record: CatalogRecord) => string + @Input() metadataQualityDisplay: MetadataQualityDisplay @Output() mdSelect = new EventEmitter() } diff --git a/libs/ui/widgets/src/index.ts b/libs/ui/widgets/src/index.ts index 330fad83cb..9ce0ad22f6 100644 --- a/libs/ui/widgets/src/index.ts +++ b/libs/ui/widgets/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/ui-widgets.module' +export * from './lib/progress-bar/progress-bar.component' export * from './lib/loading-mask/loading-mask.component' diff --git a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.css b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.css index e69de29bb2..8b5f7cd281 100644 --- a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.css +++ b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.css @@ -0,0 +1,3 @@ +.text-4 { + font-weight: var(--progress-bar-font-weight, 'bold'); +} diff --git a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.html b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.html index 930b99a4f4..ac364a9af7 100644 --- a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.html +++ b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.html @@ -5,7 +5,7 @@ color.innerBar }} my-1 mx-1 transition-width duration-500 ease-in-out rounded-t-md rounded-b-md shadow-xl" > -
+
{{ progress }}%
diff --git a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.ts b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.ts index 167921bcc6..f4cf16c0f3 100644 --- a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.ts +++ b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from '@angular/core' interface ColorScheme { outerBar: string innerBar: string + text: string } @Component({ @@ -24,16 +25,19 @@ export class ProgressBarComponent { return { outerBar: 'bg-gray-200', innerBar: 'bg-gray-100', + text: 'text-gray-900', } case 'primary': return { outerBar: 'bg-primary', innerBar: 'bg-primary-lighter', + text: 'text-white', } case 'secondary': return { outerBar: 'bg-secondary', innerBar: 'bg-secondary-lighter', + text: 'text-white', } } } diff --git a/libs/util/app-config/src/lib/app-config.ts b/libs/util/app-config/src/lib/app-config.ts index f84f694b80..1652097527 100644 --- a/libs/util/app-config/src/lib/app-config.ts +++ b/libs/util/app-config/src/lib/app-config.ts @@ -12,6 +12,7 @@ import { LayerConfig, MapConfig, SearchConfig, + MetadataQualityConfig, ThemeConfig, } from './model' import { TranslateCompiler, TranslateLoader } from '@ngx-translate/core' @@ -51,6 +52,16 @@ export function getOptionalSearchConfig(): SearchConfig | null { return searchConfig } +let metadataQualityConfig: MetadataQualityConfig = null +export function getMetadataQualityConfig(): MetadataQualityConfig { + return ( + metadataQualityConfig || + ({ + ENABLED: false, + } as MetadataQualityConfig) + ) +} + let customTranslations: CustomTranslationsAllLanguages = null export function getCustomTranslations(langCode: string): CustomTranslations { @@ -228,6 +239,51 @@ export function loadAppConfig() { ADVANCED_FILTERS: parsedSearchSection.advanced_filters, } as SearchConfig) + const parsedMetadataQualitySection = parseConfigSection( + parsed, + 'metadata-quality', + [], + [ + 'enabled', + 'sortable', + 'display_widget_in_detail', + 'display_widget_in_search', + 'display_title', + 'display_description', + 'display_topic', + 'display_keywords', + 'display_legal_constraints', + 'display_contact', + 'display_update_frequency', + 'display_organisation', + ], + warnings, + errors + ) + metadataQualityConfig = + parsedMetadataQualitySection === null + ? null + : ({ + ENABLED: parsedMetadataQualitySection.enabled, + SORTABLE: parsedMetadataQualitySection.sortable, + DISPLAY_WIDGET_IN_DETAIL: + parsedMetadataQualitySection.display_widget_in_detail, + DISPLAY_WIDGET_IN_SEARCH: + parsedMetadataQualitySection.display_widget_in_search, + DISPLAY_TITLE: parsedMetadataQualitySection.display_title, + DISPLAY_DESCRIPTION: + parsedMetadataQualitySection.display_description, + DISPLAY_TOPIC: parsedMetadataQualitySection.display_topic, + DISPLAY_KEYWORDS: parsedMetadataQualitySection.display_keywords, + DISPLAY_LEGAL_CONSTRAINTS: + parsedMetadataQualitySection.display_legal_constraints, + DISPLAY_CONTACT: parsedMetadataQualitySection.display_contact, + DISPLAY_UPDATE_FREQUENCY: + parsedMetadataQualitySection.display_update_frequency, + DISPLAY_ORGANISATION: + parsedMetadataQualitySection.display_organisation, + } as MetadataQualityConfig) + customTranslations = parseTranslationsConfigSection( parsed, 'translations' diff --git a/libs/util/app-config/src/lib/model.ts b/libs/util/app-config/src/lib/model.ts index a259d16b99..a6854bea30 100644 --- a/libs/util/app-config/src/lib/model.ts +++ b/libs/util/app-config/src/lib/model.ts @@ -51,6 +51,21 @@ export interface SearchConfig { ADVANCED_FILTERS?: [] } +export interface MetadataQualityConfig { + ENABLED: boolean + SORTABLE: boolean + DISPLAY_WIDGET_IN_DETAIL: boolean + DISPLAY_WIDGET_IN_SEARCH: boolean + DISPLAY_TITLE: boolean + DISPLAY_DESCRIPTION: boolean + DISPLAY_TOPIC: boolean + DISPLAY_KEYWORDS: boolean + DISPLAY_LEGAL_CONSTRAINTS: boolean + DISPLAY_CONTACT: boolean + DISPLAY_UPDATE_FREQUENCY: boolean + DISPLAY_ORGANISATION: boolean +} + export type CustomTranslations = { [translationKey: string]: string } export type CustomTranslationsAllLanguages = { [lang: string]: CustomTranslations diff --git a/translations/en.json b/translations/en.json index a093024676..3098e2b5c2 100644 --- a/translations/en.json +++ b/translations/en.json @@ -187,12 +187,30 @@ "record.metadata.related": "Related records", "record.metadata.sheet": "Original metadata sheet", "record.metadata.status": "status", - "record.metadata.title": "title", + "record.metadata.title": "Title", "record.metadata.updateFrequency": "Update Frequency", "record.metadata.updateStatus": "Update Status", "record.metadata.updatedOn": "Updated On", "record.metadata.usage": "Usage & constraints", "record.metadata.noUsage": "No usage conditions specified for this record.", + "record.metadata.quality": "Metadata Quality", + "record.metadata.quality.details": "Details", + "record.metadata.quality.title.success": "Title is completed", + "record.metadata.quality.title.failed": "Title is not completed", + "record.metadata.quality.description.success": "Description is completed", + "record.metadata.quality.description.failed": "Description is not completed", + "record.metadata.quality.topic.success": "Topic is completed", + "record.metadata.quality.topic.failed": "Topic is not completed", + "record.metadata.quality.keywords.success": "Keywords are completed", + "record.metadata.quality.keywords.failed": "Keywords are not completed", + "record.metadata.quality.legalConstraints.success": "Legal constraints are completed", + "record.metadata.quality.legalConstraints.failed": "Legal constraints are not completed", + "record.metadata.quality.contact.success": "Contact is completed", + "record.metadata.quality.contact.failed": "Contact is not completed", + "record.metadata.quality.updateFrequency.success": "Update frequency is completed", + "record.metadata.quality.updateFrequency.failed": "Update frequency is not completed", + "record.metadata.quality.organisation.success": "Organisation is completed", + "record.metadata.quality.organisation.failed": "Organisation is not completed", "record.more.details": "Read more", "record.tab.chart": "Chart", "record.tab.data": "Table", @@ -207,6 +225,7 @@ "results.sortBy.dateStamp": "Most recent", "results.sortBy.popularity": "Popularity", "results.sortBy.relevancy": "Relevancy", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "Suggestions could not be fetched:", "search.error.couldNotReachApi": "GeoNetwork API could not be reached", "search.error.receivedError": "An error was received", diff --git a/translations/es.json b/translations/es.json index 4efd6e5dd6..5db5548e26 100644 --- a/translations/es.json +++ b/translations/es.json @@ -192,6 +192,24 @@ "record.metadata.updatedOn": "", "record.metadata.usage": "", "record.metadata.noUsage": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", + "record.metadata.quality.title.success": "", + "record.metadata.quality.title.failed": "", + "record.metadata.quality.description.success": "", + "record.metadata.quality.description.failed": "", + "record.metadata.quality.topic.success": "", + "record.metadata.quality.topic.failed": "", + "record.metadata.quality.keywords.success": "", + "record.metadata.quality.keywords.failed": "", + "record.metadata.quality.legalConstraints.success": "", + "record.metadata.quality.legalConstraints.failed": "", + "record.metadata.quality.contact.success": "", + "record.metadata.quality.contact.failed": "", + "record.metadata.quality.updateFrequency.success": "", + "record.metadata.quality.updateFrequency.failed": "", + "record.metadata.quality.organisation.success": "", + "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -206,6 +224,7 @@ "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", "results.sortBy.relevancy": "", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", diff --git a/translations/fr.json b/translations/fr.json index 9070f2345c..ef079f707e 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -187,12 +187,30 @@ "record.metadata.related": "Voir aussi", "record.metadata.sheet": "Fiche de métadonnées d'origine", "record.metadata.status": "statut", - "record.metadata.title": "titre", + "record.metadata.title": "Titre", "record.metadata.updateFrequency": "Fréquence de mise à jour", "record.metadata.updateStatus": "Statut de mise à jour", "record.metadata.updatedOn": "Dernière mise à jour", "record.metadata.usage": "Conditions d'utilisation", "record.metadata.noUsage": "Aucune condition d'utilisation spécifiée pour ces données", + "record.metadata.quality": "Qualité des métadonnées", + "record.metadata.quality.details": "Détails", + "record.metadata.quality.title.success": "Titre est renseigné", + "record.metadata.quality.title.failed": "Titre n'est pas renseigné", + "record.metadata.quality.description.success": "Description est renseignée", + "record.metadata.quality.description.failed": "Description n'est pas renseignée", + "record.metadata.quality.topic.success": "Thème est renseigné", + "record.metadata.quality.topic.failed": "Thème n'est pas renseigné", + "record.metadata.quality.keywords.success": "Mots clés sont renseignés", + "record.metadata.quality.keywords.failed": "Mots clés ne sont pas renseignés", + "record.metadata.quality.legalConstraints.success": "Contraintes légales sont renseignées", + "record.metadata.quality.legalConstraints.failed": "Contraintes légales ne sont pas renseignées", + "record.metadata.quality.contact.success": "Contact est renseigné", + "record.metadata.quality.contact.failed": "Contact n'est pas renseigné", + "record.metadata.quality.updateFrequency.success": "Fréquence de mise à jour est renseignée", + "record.metadata.quality.updateFrequency.failed": "Fréquence de mise à jour n'est pas renseignée", + "record.metadata.quality.organisation.success": "Producteur est renseigné", + "record.metadata.quality.organisation.failed": "Producteur n'est pas renseigné", "record.more.details": "Détails", "record.tab.chart": "Graphique", "record.tab.data": "Tableau", @@ -207,6 +225,7 @@ "results.sortBy.dateStamp": "Plus récent", "results.sortBy.popularity": "Popularité", "results.sortBy.relevancy": "Pertinence", + "results.sortBy.qualityScore": "Indicateur de qualité", "search.autocomplete.error": "Les suggestions ne peuvent pas être récupérées", "search.error.couldNotReachApi": "Problème de connexion à l'API GeoNetwork", "search.error.receivedError": "Erreur retournée", diff --git a/translations/it.json b/translations/it.json index 7a8dc9ccdf..b8dbdab515 100644 --- a/translations/it.json +++ b/translations/it.json @@ -192,6 +192,24 @@ "record.metadata.updatedOn": "", "record.metadata.usage": "", "record.metadata.noUsage": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", + "record.metadata.quality.title.success": "", + "record.metadata.quality.title.failed": "", + "record.metadata.quality.description.success": "", + "record.metadata.quality.description.failed": "", + "record.metadata.quality.topic.success": "", + "record.metadata.quality.topic.failed": "", + "record.metadata.quality.keywords.success": "", + "record.metadata.quality.keywords.failed": "", + "record.metadata.quality.legalConstraints.success": "", + "record.metadata.quality.legalConstraints.failed": "", + "record.metadata.quality.contact.success": "", + "record.metadata.quality.contact.failed": "", + "record.metadata.quality.updateFrequency.success": "", + "record.metadata.quality.updateFrequency.failed": "", + "record.metadata.quality.organisation.success": "", + "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -206,6 +224,7 @@ "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", "results.sortBy.relevancy": "", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", diff --git a/translations/nl.json b/translations/nl.json index 34d4420419..98c31dd2ca 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -192,6 +192,24 @@ "record.metadata.updatedOn": "", "record.metadata.usage": "", "record.metadata.noUsage": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", + "record.metadata.quality.title.success": "", + "record.metadata.quality.title.failed": "", + "record.metadata.quality.description.success": "", + "record.metadata.quality.description.failed": "", + "record.metadata.quality.topic.success": "", + "record.metadata.quality.topic.failed": "", + "record.metadata.quality.keywords.success": "", + "record.metadata.quality.keywords.failed": "", + "record.metadata.quality.legalConstraints.success": "", + "record.metadata.quality.legalConstraints.failed": "", + "record.metadata.quality.contact.success": "", + "record.metadata.quality.contact.failed": "", + "record.metadata.quality.updateFrequency.success": "", + "record.metadata.quality.updateFrequency.failed": "", + "record.metadata.quality.organisation.success": "", + "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -206,6 +224,7 @@ "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", "results.sortBy.relevancy": "", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", diff --git a/translations/pt.json b/translations/pt.json index 0291fee6f1..8ff6c8189e 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -192,6 +192,24 @@ "record.metadata.updatedOn": "", "record.metadata.usage": "", "record.metadata.noUsage": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", + "record.metadata.quality.title.success": "", + "record.metadata.quality.title.failed": "", + "record.metadata.quality.description.success": "", + "record.metadata.quality.description.failed": "", + "record.metadata.quality.topic.success": "", + "record.metadata.quality.topic.failed": "", + "record.metadata.quality.keywords.success": "", + "record.metadata.quality.keywords.failed": "", + "record.metadata.quality.legalConstraints.success": "", + "record.metadata.quality.legalConstraints.failed": "", + "record.metadata.quality.contact.success": "", + "record.metadata.quality.contact.failed": "", + "record.metadata.quality.updateFrequency.success": "", + "record.metadata.quality.updateFrequency.failed": "", + "record.metadata.quality.organisation.success": "", + "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -206,6 +224,7 @@ "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", "results.sortBy.relevancy": "", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "",