From d7ebdcd2f05275bb0f2980253204e808569a4a70 Mon Sep 17 00:00:00 2001 From: Guillaume Keime Date: Mon, 21 Aug 2023 11:02:41 +0200 Subject: [PATCH] rebase lastest main --- apps/datahub/src/styles.css | 3 + apps/search/src/assets/i18n/en.json | 2 +- apps/webcomponents/src/assets/i18n/en.json | 1 + conf/default.toml | 31 +++++ .../record-metadata.component.html | 1 + .../record-metadata.component.ts | 10 +- .../src/lib/sort-by/sort-by.component.ts | 12 +- .../mapper/elasticsearch.field.mapper.ts | 112 +++++++++++++++--- .../utils/mapper/elasticsearch.mapper.spec.ts | 45 ++++--- libs/ui/elements/src/index.ts | 2 + .../metadata-quality-info.component.html | 6 + .../metadata-quality-info.component.spec.ts | 27 +++++ .../metadata-quality-info.component.ts | 34 ++++++ .../metadata-quality.component.css | 26 ++++ .../metadata-quality.component.html | 21 ++++ .../metadata-quality.component.spec.ts | 54 +++++++++ .../metadata-quality.component.stories.ts | 35 ++++++ .../metadata-quality.component.ts | 29 +++++ .../ui/elements/src/lib/ui-elements.module.ts | 6 + .../record-preview-row.component.html | 8 +- .../record-preview-row.component.ts | 8 ++ .../progress-bar/progress-bar.component.html | 2 +- .../progress-bar.component.spec.ts | 7 ++ .../progress-bar.component.stories.ts | 1 + .../progress-bar/progress-bar.component.ts | 6 + libs/util/app-config/src/lib/app-config.ts | 57 +++++++++ libs/util/app-config/src/lib/model.ts | 16 +++ .../shared/src/lib/elasticsearch/constant.ts | 7 ++ .../elasticsearch.service.spec.ts | 7 ++ libs/util/shared/src/lib/fixtures/records.ts | 17 +++ .../shared/src/lib/models/search.model.ts | 3 + .../shared/src/lib/models/sort-by.model.ts | 1 + translations/en.json | 21 +++- translations/es.json | 19 +++ translations/fr.json | 21 +++- translations/it.json | 19 +++ translations/nl.json | 19 +++ translations/pt.json | 19 +++ 38 files changed, 672 insertions(+), 43 deletions(-) create mode 100644 libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.html create mode 100644 libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.spec.ts create mode 100644 libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.ts create mode 100644 libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css create mode 100644 libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html create mode 100644 libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts create mode 100644 libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts create mode 100644 libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts diff --git a/apps/datahub/src/styles.css b/apps/datahub/src/styles.css index f290ad9d70..31624948a3 100644 --- a/apps/datahub/src/styles.css +++ b/apps/datahub/src/styles.css @@ -7,6 +7,9 @@ body { margin: 0; } +.container-xs { + max-width: calc(100% - 170px); +} .container-sm { max-width: 640px; } diff --git a/apps/search/src/assets/i18n/en.json b/apps/search/src/assets/i18n/en.json index ddc1c8a84a..93e3da443c 100644 --- a/apps/search/src/assets/i18n/en.json +++ b/apps/search/src/assets/i18n/en.json @@ -25,4 +25,4 @@ "search.field.any.placeholder": "Search datasets, services and maps ...", "search.field.sortBy": "Sort by", "search.loading": "Loading ..." -} +} \ No newline at end of file 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..21f6f46036 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -59,6 +59,9 @@ background_color = "#fdfbff" # title_font = "'My Custom Title Font', fallback-font-title" # fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&family=Permanent+Marker&display=swap" +# This optional parameter allow you to change the default class on progress bar, by default is font-bold +# progress_bar_text_class = 'font-normal' + ### SEARCH SETTINGS # This section contains settings used for fine-tuning the search experience @@ -95,6 +98,34 @@ 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 wudget appear in 2 location in search list and in detail page +# display_widget_in_detail = false // allow you to hide the widget in detail +# display_widget_in_search = false // allow you to hide the widget in search list +# If you want see the widget in the two location, 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/feature/record/src/lib/record-metadata/record-metadata.component.html b/libs/feature/record/src/lib/record-metadata/record-metadata.component.html index 11d5a65327..eb5d208453 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 @@ -26,6 +26,7 @@
+ = { id: (output, source) => ({ @@ -42,7 +44,8 @@ export class ElasticsearchFieldMapper { uuid: (output, source) => { const uuid = selectField(source, 'uuid') const metadataUrl = this.metadataUrlService.getUrl(uuid) - return { ...output, uuid, metadataUrl } + const qualityScore = this.calculateQualityScore(source) + return { ...output, uuid, metadataUrl, qualityScore } }, resourceTitleObject: (output, source) => ({ ...output, @@ -71,6 +74,12 @@ export class ElasticsearchFieldMapper { ) ), }), + cl_topic: (output, source) => ({ + ...output, + topic: getAsArray( + selectField(source, 'cl_topic') + ).map((cl_topic) => selectTranslatedValue(cl_topic)), + }), cl_status: (output, source) => ({ ...output, updateStatus: selectTranslatedValue( @@ -130,12 +139,21 @@ export class ElasticsearchFieldMapper { }), MD_ConstraintsUseLimitationObject: (output, source) => this.constraintField('MD_ConstraintsUseLimitationObject', output, source), - MD_LegalConstraintsUseLimitationObject: (output, source) => - this.constraintField( - 'MD_LegalConstraintsUseLimitationObject', - output, - source - ), + MD_LegalConstraintsUseLimitationObject: (output, source) => { + const legalConstraints = getAsArray( + selectField(source, 'MD_LegalConstraintsUseLimitationObject') + ).map((MD_LegalConstraintsUseLimitationObject) => selectTranslatedValue(MD_LegalConstraintsUseLimitationObject)); + let prevConstraints = output.constraints || []; + const constraints = { + ...prevConstraints, + ...legalConstraints + }; + return { + ...output, + legalConstraints, + constraints + } + }, MD_LegalConstraintsOtherConstraintsObject: (output, source) => this.constraintField( 'MD_LegalConstraintsOtherConstraintsObject', @@ -171,16 +189,78 @@ export class ElasticsearchFieldMapper { }), } - private genericField = (output) => output + private calculateQualityScore = (source) => { + const qualityScore: number = selectField(source, 'qualityScore'); + if (qualityScore != null) { + return qualityScore; + } + const metadataQualityConfig: MetadataQualityConfig = getMetadataQualityConfig(); + let total = 0; + let success = 0; + const check = (name: string) => { + const display = metadataQualityConfig[`DISPLAY_${name}`] !== false; + if (display) total++; + return display; + } + if (check('TITLE')) { + if (selectField(source, 'resourceTitleObject')) { + success++; + } + } + if (check('DESCRIPTION')) { + if (selectFallback(selectTranslatedField(source, 'resourceAbstractObject'), 'no title')) { + success++; + } + } + const contact = mapContact( + getFirstValue(selectField(source, 'contact')), + source + ); + if (check('ORGANISATION')) { + if (contact?.organisation) { + success++; + } + } + if (check('CONTACT')) { + if (contact?.email) { + success++; + } + } + if (check('TOPIC')) { + if (selectField(source, 'cl_topic')?.length > 0) { + success++; + } + } + if (check('KEYWORDS')) { + if (selectField(source, 'tag')?.length > 0) { + success++; + } + } + if (check('UPDATE_FREQUENCY')) { + if (getFirstValue(selectField(source, 'cl_maintenanceAndUpdateFrequency'))) { + success++; + } + } + if (check("LEGAL_CONSTRAINTS")) { + if (selectField(source, 'MD_LegalConstraintsUseLimitationObject')?.length > 0) { + success++; + } + } + return Math.round(success * 100 / total); + } - private constraintField = (fieldName: string, output, source) => ({ - ...output, - constraints: [ - ...(output.constraints || []), - ...selectField(source, fieldName).map(selectTranslatedValue), - ], - }) + private genericField = (output) => output + private constraintField = (fieldName: string, output, source) => { + const constraints = Array.isArray(output.constraints) ? output.constraints : []; + const fieldValues = selectField(source, fieldName); + const translatedValues = fieldValues.map(selectTranslatedValue); + const updatedConstraints = [...constraints, ...translatedValues]; + return { + ...output, + constraints: updatedConstraints, + }; + } getMappingFn(fieldName: string) { return fieldName in this.fields ? this.fields[fieldName] : this.genericField } diff --git a/libs/feature/search/src/lib/utils/mapper/elasticsearch.mapper.spec.ts b/libs/feature/search/src/lib/utils/mapper/elasticsearch.mapper.spec.ts index 1774166759..bba8d7cc93 100644 --- a/libs/feature/search/src/lib/utils/mapper/elasticsearch.mapper.spec.ts +++ b/libs/feature/search/src/lib/utils/mapper/elasticsearch.mapper.spec.ts @@ -20,23 +20,23 @@ class OrganisationsServiceMock { of( 'contact' in source ? { - ...record, - contact: { - name: 'Main Contact', + ...record, + contact: { + name: 'Main Contact', + email: 'q2suppor@ifremer.fr', + organisation: source.contact[0].organisation, + }, + resourceContacts: [ + { + name: 'Resource Contact 1', email: 'q2suppor@ifremer.fr', - organisation: source.contact[0].organisation, }, - resourceContacts: [ - { - name: 'Resource Contact 1', - email: 'q2suppor@ifremer.fr', - }, - { - name: 'Resource Contact 2', - email: 'q2suppor@ifremer.fr', - }, - ], - } + { + name: 'Resource Contact 2', + email: 'q2suppor@ifremer.fr', + }, + ], + } : record ) ) @@ -78,8 +78,13 @@ describe('ElasticsearchMapper', () => { abstract: 'The grid is based on proposal ', id: '12456', metadataUrl: 'url', +<<<<<<< HEAD thumbnailUrl: 'https://sdi.eea.europa.eu/public/catalogue-graphic-overview/20e9e1a1-83c1-4f13-89ef-c19767d6ee18f.png', +======= + qualityScore: 25, + thumbnailUrl: 'data:image/png;base64,', +>>>>>>> 00e9c4b0 (fix asked by camp2camp) title: 'EEA reference grid for Germany (10km), May 2013', uuid: '20e9e1a1-83c1-4f13-89ef-c19767d6ee18f', catalogUuid: '6731be1e-6533-44e0-9b8a-580b45e36e80', @@ -101,6 +106,7 @@ describe('ElasticsearchMapper', () => { hasDownloads: false, hasMaps: false, links: [], + qualityScore: 25 }, ]) }) @@ -432,9 +438,13 @@ describe('ElasticsearchMapper', () => { ], metadataUrl: 'url', ownerInfo: 'testadmin|ADMIN|Test|Administrator', + qualityScore: 100, thumbnailUrl: 'https://sextant.ifremer.fr/geonetwork/srv/api/records/cf5048f6-5bbf-4e44-ba74-e6f429af51ea/attachments/parametres.gif', title: 'Surval - Données par paramètre', + topic: [ + 'Océans', + ], uuid: 'cf5048f6-5bbf-4e44-ba74-e6f429af51ea', contact: { name: 'Main Contact', @@ -516,11 +526,12 @@ describe('ElasticsearchMapper', () => { 'Sète', 'La Rochelle', ], + legalConstraints: [ + "Restriction légale d'utilisation à préciser", + ], lineage: 'Les données sont bancarisées dans la base de données Quadrige.', constraints: [ - 'Restriction lié à l’exercice du droit moral', - "Restriction légale d'utilisation à préciser", 'Pas de restriction d’accès public', 'Licence Ouverte version 2.0 https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf', ], 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-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..43ffb3bef6 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.html @@ -0,0 +1,6 @@ + \ No newline at end of file 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..c0f0db07fc --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { UtilSharedModule } from '@geonetwork-ui/util/shared' +import { TranslateModule } from '@ngx-translate/core' +import { ContentGhostComponent } from '../content-ghost/content-ghost.component' +import { MetadataQualityInfoComponent } from './metadata-quality-info.component' + +describe('MetadataQualityInfoComponent', () => { + let component: MetadataQualityInfoComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), UtilSharedModule], + declarations: [MetadataQualityInfoComponent, ContentGhostComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataQualityInfoComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) 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..ce803a4443 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality-info/metadata-quality-info.component.ts @@ -0,0 +1,34 @@ +import { + ChangeDetectionStrategy, + Component, + Input +} from '@angular/core' +import { MetadataQualityConfig, getMetadataQualityConfig } from '@geonetwork-ui/util/app-config'; + +@Component({ + selector: 'gn-ui-metadata-quality-info', + templateUrl: './metadata-quality-info.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MetadataQualityInfoComponent { + + metadataQualityConfig: MetadataQualityConfig = getMetadataQualityConfig(); + @Input() name: string + @Input() value: boolean + + get display() { + if (this.name) { + const nameSnakeUpper = this.name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toUpperCase(); + return this.metadataQualityConfig['DISPLAY_' + nameSnakeUpper] !== false; + } + return false; + } + + 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..a03aeeb609 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.css @@ -0,0 +1,26 @@ +:host-context(.picto) > .metadata-quality > .widget > p.text { + display: none; +} + +:host-context(.picto) > .metadata-quality > .menu { + right: 0px; +} +:host-context(.picto)> .metadata-quality > .widget gn-ui-progress-bar { + line-height: 8px; +} + +:host-context(.picto) ::ng-deep gn-ui-progress-bar .text-4 { + font-weight: normal; + font-size: 15px; +} + +.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; +} \ No newline at end of file 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..6c85219693 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html @@ -0,0 +1,21 @@ + + + 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..d3af3ac346 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts @@ -0,0 +1,54 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { UtilSharedModule } from '@geonetwork-ui/util/shared' +import { RECORDS_FULL_FIXTURE } from '@geonetwork-ui/util/shared/fixtures' +import { TranslateModule } from '@ngx-translate/core' +import { ContentGhostComponent } from '../content-ghost/content-ghost.component' +import { MetadataQualityComponent } from './metadata-quality.component' +import { By } from '@angular/platform-browser' + +describe('MetadataQualityComponent', () => { + let component: MetadataQualityComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), UtilSharedModule], + declarations: [MetadataQualityComponent, ContentGhostComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataQualityComponent) + component = fixture.componentInstance + component.metadata = RECORDS_FULL_FIXTURE[0] + 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?.contact?.organisation).toBe("Ifremer"); + }) +}) 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..c4caf98707 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts @@ -0,0 +1,35 @@ +import { + TRANSLATE_DEFAULT_CONFIG, + UtilI18nModule, +} from '@geonetwork-ui/util/i18n' +import { TranslateModule } from '@ngx-translate/core' +import { Meta, moduleMetadata, Story } from '@storybook/angular' +import { MetadataQualityComponent } from './metadata-quality.component' +import { UiElementsModule } from '../ui-elements.module' +import { RECORDS_FULL_FIXTURE } from '@geonetwork-ui/util/shared/fixtures' + +export default { + title: 'Elements/MetadataQualityComponent', + component: MetadataQualityComponent, + decorators: [ + moduleMetadata({ + imports: [ + UiElementsModule, + UtilI18nModule, + TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + ], + }), + ], +} as Meta + +const Template: Story = ( + args: MetadataQualityComponent +) => ({ + component: MetadataQualityComponent, + props: args, +}) + +export const Primary = Template.bind({}) +Primary.args = { + metadata: RECORDS_FULL_FIXTURE[1], +} 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..3f6feece60 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts @@ -0,0 +1,29 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core' +import { MetadataLink, MetadataRecord } from '@geonetwork-ui/util/shared' + +@Component({ + selector: 'gn-ui-metadata-quality', + templateUrl: './metadata-quality.component.html', + styleUrls: ['./metadata-quality.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MetadataQualityComponent { + @Input() metadata: MetadataRecord + @Input() landingPages: MetadataLink[] + @Output() keyword = new EventEmitter() + isMenuShown = false + + showMenu() { + this.isMenuShown = true + } + + hideMenu() { + this.isMenuShown = false + } +} diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index d67290b3b3..44ed460169 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' @@ -48,6 +50,8 @@ import { UserPreviewComponent } from './user-preview/user-preview.component' RelatedRecordCardComponent, MetadataContactComponent, MetadataCatalogComponent, + MetadataQualityComponent, + MetadataQualityInfoComponent, SearchResultsErrorComponent, PaginationComponent, ThumbnailComponent, @@ -64,6 +68,8 @@ import { UserPreviewComponent } from './user-preview/user-preview.component' RelatedRecordCardComponent, MetadataContactComponent, MetadataCatalogComponent, + MetadataQualityComponent, + MetadataQualityInfoComponent, SearchResultsErrorComponent, PaginationComponent, ThumbnailComponent, 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 3fdc6362fc..abbcbf7bd0 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,7 @@
{{ contact?.organisation }}
@@ -52,10 +53,11 @@ >map
+
+ +
+ class="text-right col-start-3 row-start-4 sm:absolute sm:col-start-2 sm:row-start-1 sm:top-[-1.125em] sm:right-[0.4em]"> -
+
{{ progress }}%
diff --git a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.spec.ts b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.spec.ts index c54f3ae446..9b7b3e1ef0 100644 --- a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.spec.ts +++ b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.spec.ts @@ -2,6 +2,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { ProgressBarComponent } from './progress-bar.component' +jest.mock('@geonetwork-ui/util/app-config', () => ({ + getThemeConfig: () => ({ + PROGRESS_BAR_TEXT_CLASS: '', + }), + isConfigLoaded: jest.fn(() => true), +})) + describe('ProgressBarComponent', () => { let component: ProgressBarComponent let fixture: ComponentFixture diff --git a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.stories.ts b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.stories.ts index 4a93e79ca0..a7d790c460 100644 --- a/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.stories.ts +++ b/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.stories.ts @@ -3,6 +3,7 @@ import { ProgressBarComponent } from './progress-bar.component' import { importProvidersFrom } from '@angular/core' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' + export default { title: 'Widgets/ProgressBarComponent', component: ProgressBarComponent, 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..e1b5f1f94e 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 @@ -1,8 +1,10 @@ import { Component, Input } from '@angular/core' +import { getThemeConfig, isConfigLoaded } from '@geonetwork-ui/util/app-config' interface ColorScheme { outerBar: string innerBar: string + text: string } @Component({ @@ -13,6 +15,7 @@ interface ColorScheme { export class ProgressBarComponent { @Input() value = 0 @Input() type: 'primary' | 'secondary' | 'default' = 'default' + textClass = isConfigLoaded() ? getThemeConfig().PROGRESS_BAR_TEXT_CLASS : 'font-bold' get progress() { return this.value > 0 ? (this.value < 100 ? this.value : 100) : 0 @@ -24,16 +27,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 68644108ed..0a0fc33cd2 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' @@ -46,6 +47,13 @@ export function getOptionalSearchConfig(): SearchConfig | null { return searchConfig } +let metadataQualityConfig: MetadataQualityConfig = null +export function getMetadataQualityConfig(): MetadataQualityConfig | null { + return metadataQualityConfig || ({ + ENABLED: false + } as MetadataQualityConfig); +} + let customTranslations: CustomTranslationsAllLanguages = null export function getCustomTranslations(langCode: string): CustomTranslations { @@ -167,6 +175,7 @@ export function loadAppConfig() { 'fonts_stylesheet_url', 'thumbnail_placeholder', 'header_background', + 'progress_bar_text_class' ], warnings, errors @@ -186,6 +195,7 @@ export function loadAppConfig() { TITLE_FONT: parsedThemeSection.title_font, MAIN_FONT: parsedThemeSection.main_font, FONTS_STYLESHEET_URL: parsedThemeSection.fonts_stylesheet_url, + PROGRESS_BAR_TEXT_CLASS: parsedThemeSection.progress_bar_text_class } as ThemeConfig) const parsedSearchSection = parseConfigSection( @@ -223,6 +233,53 @@ 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_RECORD_METADATA: parsedMetadataQualitySection.display_widget_in_detail, + DISPLAY_WIDGET_IN_PREVIEW_ROW: 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) + + searchConfig = + parsedSearchSection === null + ? null + : ({ + FILTER_GEOMETRY_DATA: parsedSearchSection.filter_geometry_data, + FILTER_GEOMETRY_URL: parsedSearchSection.filter_geometry_url, + } as SearchConfig) + 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..2f7b90a0e7 100644 --- a/libs/util/app-config/src/lib/model.ts +++ b/libs/util/app-config/src/lib/model.ts @@ -36,6 +36,7 @@ export interface ThemeConfig { MAIN_FONT?: string TITLE_FONT?: string FONTS_STYLESHEET_URL?: string + PROGRESS_BAR_TEXT_CLASS?: string } export interface SearchPreset { @@ -51,6 +52,21 @@ export interface SearchConfig { ADVANCED_FILTERS?: [] } +export interface MetadataQualityConfig { + ENABLED: boolean + SORTABLE: boolean + DISPLAY_WIDGET_IN_RECORD_METADATA: boolean + DISPLAY_WIDGET_IN_PREVIEW_ROW: 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/libs/util/shared/src/lib/elasticsearch/constant.ts b/libs/util/shared/src/lib/elasticsearch/constant.ts index 380530a1ff..88bcef31d4 100644 --- a/libs/util/shared/src/lib/elasticsearch/constant.ts +++ b/libs/util/shared/src/lib/elasticsearch/constant.ts @@ -17,7 +17,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_SOURCE_BRIEF = [ diff --git a/libs/util/shared/src/lib/elasticsearch/elasticsearch.service.spec.ts b/libs/util/shared/src/lib/elasticsearch/elasticsearch.service.spec.ts index 52a3665544..90121b0de5 100644 --- a/libs/util/shared/src/lib/elasticsearch/elasticsearch.service.spec.ts +++ b/libs/util/shared/src/lib/elasticsearch/elasticsearch.service.spec.ts @@ -482,7 +482,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/util/shared/src/lib/fixtures/records.ts b/libs/util/shared/src/lib/fixtures/records.ts index 3b3ec174ad..319fca61de 100644 --- a/libs/util/shared/src/lib/fixtures/records.ts +++ b/libs/util/shared/src/lib/fixtures/records.ts @@ -159,4 +159,21 @@ export const RECORDS_FULL_FIXTURE: MetadataRecord[] = deepFreeze([ }, catalogUuid: '6731be1e-6533-44e0-9b8a-580b45e36e80', }, + { + id: '10421', + uuid: 'cf5048f6-5bbf-4e44-ba74-e6f429af51eb', + metadataUrl: 'url', + title: 'Test', + abstract: "La description du test", + updateFrequency: null, + keywords: [], + contact: { + name: 'Jean-Michel', + organisation: 'Ifremer', + email: 'q2suppor@ifremer.fr', + }, + topic: [], + legalConstraints: [], + qualityScore: 50 + } ]) diff --git a/libs/util/shared/src/lib/models/search.model.ts b/libs/util/shared/src/lib/models/search.model.ts index a25d02130a..b9a56b9bd8 100644 --- a/libs/util/shared/src/lib/models/search.model.ts +++ b/libs/util/shared/src/lib/models/search.model.ts @@ -63,6 +63,9 @@ export interface MetadataRecord { isOpenData?: boolean ownerInfo?: string isPublishedToAll?: boolean + topic?: string[], + legalConstraints?: string[] + qualityScore?: number } export enum MetadataLinkType { diff --git a/libs/util/shared/src/lib/models/sort-by.model.ts b/libs/util/shared/src/lib/models/sort-by.model.ts index 08ec0b7ce1..2a6716c4dd 100644 --- a/libs/util/shared/src/lib/models/sort-by.model.ts +++ b/libs/util/shared/src/lib/models/sort-by.model.ts @@ -2,4 +2,5 @@ export enum SortByEnum { CREATE_DATE = '-createDate', POPULARITY = '-userSavedCount', RELEVANCY = '_score', + QUALITY_SCORE = '-qualityScore' } diff --git a/translations/en.json b/translations/en.json index 5d87b7d235..1b034d9e6b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -186,12 +186,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", @@ -206,6 +224,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 7b38af7817..aa893253e2 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -186,12 +186,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", @@ -206,6 +224,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": "",