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 e4f7b3eed9..22c4fe2e62 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 @@ -101,6 +101,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 f28155e0db..c12ab5b9bf 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, @@ -24,6 +25,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/conf/default.toml b/conf/default.toml index 661eacf206..307c4c72a9 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -98,6 +98,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..abeaf9ddf4 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,11 @@ export class Gn4FieldMapper { const landingPage = getAsUrl(this.metadataUrlService.getUrl(uuid)) return { ...output, uniqueIdentifier: uuid, landingPage } }, + qualityScore: (output, source) => + this.addExtra( + { qualityScore: selectField(source, 'qualityScore') }, + output + ), resourceTitleObject: (output, source) => ({ ...output, title: selectFallback( @@ -83,6 +88,15 @@ export class Gn4FieldMapper { ], } }, + cl_topic: (output, source) => ({ + ...output, + themes: [ + ...(output.themes || []), + ...getAsArray( + selectField(source, 'cl_topic') + ).map((topic) => selectTranslatedValue(topic, this.lang3)), + ], + }), cl_status: (output, source) => ({ ...output, status: getStatusFromStatusCode( @@ -163,7 +177,10 @@ export class Gn4FieldMapper { }), inspireTheme: (output, source) => ({ ...output, - themes: getAsArray(selectField(source, 'inspireTheme_syn')), + themes: [ + ...(output.themes || []), + ...getAsArray(selectField(source, 'inspireTheme_syn')), + ], }), MD_ConstraintsUseLimitationObject: (output, source) => this.constraintField('MD_ConstraintsUseLimitationObject', output, source), @@ -249,6 +266,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..0e5e9de53f 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( @@ -635,7 +636,7 @@ describe('Gn4MetadataMapper', () => { recordCreated: new Date('2021-10-05T12:48:57.678Z'), recordUpdated: new Date('2021-10-05T12:48:57.678Z'), status: 'under_development', - themes: ['Installations de suivi environnemental'], + themes: ['Installations de suivi environnemental', 'Océans'], title: 'Surval - Données par paramètre', uniqueIdentifier: 'cf5048f6-5bbf-4e44-ba74-e6f429af51ea', updateFrequency: { diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts b/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts index bff735b7d4..9545056249 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..36ca62933b 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 75b3ce778a..90d2fba553 100644 --- a/libs/common/domain/src/lib/record/metadata.model.ts +++ b/libs/common/domain/src/lib/record/metadata.model.ts @@ -78,10 +78,12 @@ export interface BaseRecord { keywords: Array // TODO: handle thesaurus and id accessConstraints: Array useLimitations: Array + legalConstraints?: Array licenses: Array overviews: Array extras?: Record landingPage?: URL + updateFrequency?: UpdateFrequency // to add: iso19139.topicCategory // to add: canonical url @@ -156,7 +158,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..7c099af17d 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,16 +1,22 @@ -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' import { SearchService } from '../utils/service/search.service' import { filter, map } from 'rxjs/operators' +interface SortChoice { + label: string + value: string +} + @Component({ selector: 'gn-ui-sort-by', templateUrl: './sort-by.component.html', }) -export class SortByComponent { - choices = [ +export class SortByComponent implements OnInit { + @Input() isQualitySortable: boolean + choices: SortChoice[] = [ { label: marker('results.sortBy.relevancy'), value: SortByEnum.RELEVANCY.join(','), @@ -34,6 +40,15 @@ export class SortByComponent { private searchService: SearchService ) {} + ngOnInit(): void { + if (this.isQualitySortable) { + this.choices.push({ + label: marker('results.sortBy.qualityScore'), + value: SortByEnum.QUALITY_SCORE.join(','), + }) + } + } + 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 5b7c9c83c0..f85267c13f 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-item/metadata-quality-item.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-quality-item/metadata-quality-item.component.html b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html new file mode 100644 index 0000000000..bfc88db01e --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html @@ -0,0 +1,4 @@ +
+ {{ icon }} +

{{ labelKey | translate }}

+
diff --git a/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.spec.ts b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.spec.ts new file mode 100644 index 0000000000..124ca72ada --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.spec.ts @@ -0,0 +1,91 @@ +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 { MetadataQualityItemComponent } from './metadata-quality-item.component' +import { By } from '@angular/platform-browser' +import { CommonModule } from '@angular/common' +import { MatIconModule } from '@angular/material/icon' + +describe('MetadataQualityInfoComponent', () => { + let component: MetadataQualityItemComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MetadataQualityItemComponent], + imports: [ + UtilSharedModule, + CommonModule, + MatIconModule, + UtilI18nModule, + TranslateModule.forRoot(), + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataQualityItemComponent) + 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-item/metadata-quality-item.component.ts b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.ts new file mode 100644 index 0000000000..0f0e1d60d7 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' + +export interface MetadataQualityItem { + name: string + value: boolean +} + +@Component({ + selector: 'gn-ui-metadata-quality-item', + templateUrl: './metadata-quality-item.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MetadataQualityItemComponent implements MetadataQualityItem { + @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..b049cf6f2b --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css @@ -0,0 +1,3 @@ +:host gn-ui-progress-bar { + --progress-bar-font-weight: 'normal'; +} 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..f24ab4e8b6 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html @@ -0,0 +1,27 @@ + 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..c258fef2cc --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts @@ -0,0 +1,114 @@ +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 { MetadataQualityItemComponent } from '../metadata-quality-item/metadata-quality-item.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 expectedItems = [ + { name: 'title', value: true }, + { name: 'description', value: true }, + { name: 'topic', value: true }, + { 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, + MetadataQualityItemComponent, + 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 + component.initialize() + 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.items) + const expected$ = cold('(a|)', { a: expectedItems }) + expect(infoObservable).toBeObservable(expected$) + }) + + it('should display sub-components with correct inputs', () => { + const metadataItems = fixture.debugElement.queryAll( + By.directive(MetadataQualityItemComponent) + ) + const expectedItemsCount = expectedItems.length + expect(metadataItems.length).toBe(expectedItemsCount) + + for (let i = 0; i < expectedItemsCount; i++) { + const el = metadataItems[i].componentInstance + expect(el.name).toBe(expectedItems[i].name) + expect(el.value).toBe(expectedItems[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..6fae4ff811 --- /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 { MetadataQualityItemComponent } from '../metadata-quality-item/metadata-quality-item.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, MetadataQualityItemComponent], + 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..926641f3bd --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts @@ -0,0 +1,84 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + SimpleChanges, +} from '@angular/core' +import { MetadataQualityItem } from '../metadata-quality-item/metadata-quality-item.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 { + @Input() metadata: Partial + @Input() smaller = false + @Input() metadataQualityDisplay: MetadataQualityDisplay + + items: MetadataQualityItem[] = [] + + isMenuShown = false + + get qualityScore() { + const qualityScore = this.metadata?.extras?.qualityScore + return typeof qualityScore === 'number' + ? qualityScore + : this.calculatedQualityScore + } + + get calculatedQualityScore(): number { + return Math.round( + (this.items.filter(({ value }) => value === true).length * 100) / + this.items.length + ) + } + + showMenu() { + this.isMenuShown = true + } + + hideMenu() { + this.isMenuShown = false + } + + private add(name: string, value: boolean) { + if (this.metadataQualityDisplay?.[name] !== false) { + this.items.push({ name, value }) + } + } + + initialize() { + const contact = this.metadata?.contacts?.[0] + this.items = [] + this.add('title', !!this.metadata?.title) + this.add('description', !!this.metadata?.abstract) + this.add('topic', this.metadata?.themes?.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.initialize() + } + } +} diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index 6e0566fa54..b0faaf4305 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 { MetadataQualityItemComponent } from './metadata-quality-item/metadata-quality-item.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, + MetadataQualityItemComponent, SearchResultsErrorComponent, PaginationComponent, ThumbnailComponent, @@ -68,6 +72,8 @@ import { PaginationButtonsComponent } from './pagination-buttons/pagination-butt RelatedRecordCardComponent, MetadataContactComponent, MetadataCatalogComponent, + MetadataQualityComponent, + MetadataQualityItemComponent, 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 8aaf6837ba..201369a509 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..6be88cf666 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,8 @@ 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..912f12e2da 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 @@ +.font-bold { + 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..b080b2b0ca 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 a5161f5937..092c5b1583 100644 --- a/translations/en.json +++ b/translations/en.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "Data Update Status", "record.metadata.updatedOn": "Last Data Information Update", "record.metadata.usage": "Usage & constraints", + "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", @@ -221,6 +239,7 @@ "results.sortBy.dateStamp": "Most recent", "results.sortBy.popularity": "Popularity", "results.sortBy.relevancy": "Relevancy", + "results.sortBy.qualityScore": "Quality score", "search.autocomplete.error": "Suggestions could not be fetched:", "search.error.couldNotReachApi": "The API could not be reached", "search.error.receivedError": "An error was received", diff --git a/translations/es.json b/translations/es.json index 99b30dbc7b..6cf5eefc40 100644 --- a/translations/es.json +++ b/translations/es.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", + "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": "", @@ -221,6 +239,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 3624304b54..22d8ae5069 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "Statut de mise à jour des données", "record.metadata.updatedOn": "Dernière mise à jour des informations sur les données", "record.metadata.usage": "Conditions d'utilisation", + "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", @@ -221,6 +239,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", "search.error.receivedError": "Erreur retournée", diff --git a/translations/it.json b/translations/it.json index 87e64ddc88..4afca57dd2 100644 --- a/translations/it.json +++ b/translations/it.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", + "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": "", @@ -221,6 +239,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 66028d6bbe..4ce7f6cac9 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", + "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": "", @@ -221,6 +239,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 42f5e7ebec..a7f20c0585 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -207,6 +207,24 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", + "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": "", @@ -221,6 +239,7 @@ "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", "results.sortBy.relevancy": "", + "results.sortBy.qualityScore": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "",