From 9736e3d144d949fb858688f082c66d5cf57d9d75 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 26 Aug 2024 14:36:45 +0200 Subject: [PATCH 1/7] feat(editor): Added field contacts for metadata --- .../editor/src/lib/+state/editor.reducer.ts | 2 +- .../form-field-contacts.component.css | 0 .../form-field-contacts.component.html | 71 ++++++ .../form-field-contacts.component.spec.ts | 224 ++++++++++++++++++ .../form-field-contacts.component.ts | 217 +++++++++++++++++ .../form-field/form-field.component.html | 5 + libs/feature/editor/src/lib/fields.config.ts | 9 +- 7 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.css create mode 100644 libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html create mode 100644 libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts create mode 100644 libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts diff --git a/libs/feature/editor/src/lib/+state/editor.reducer.ts b/libs/feature/editor/src/lib/+state/editor.reducer.ts index d2b4e96a5c..dfb4835ae5 100644 --- a/libs/feature/editor/src/lib/+state/editor.reducer.ts +++ b/libs/feature/editor/src/lib/+state/editor.reducer.ts @@ -38,7 +38,7 @@ export const initialEditorState: EditorState = { saveError: null, changedSinceSave: false, editorConfig: DEFAULT_CONFIGURATION, - currentPage: 0, + currentPage: 2, //todo: remove before merge } const reducer = createReducer( diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.css b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html new file mode 100644 index 0000000000..86db0afdf0 --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html @@ -0,0 +1,71 @@ +
+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ editor.record.form.field.contacts.noContact +
+
diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts new file mode 100644 index 0000000000..0f21913bb4 --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts @@ -0,0 +1,224 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { FormFieldContactsComponent } from './form-field-contacts.component' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { BehaviorSubject } from 'rxjs' +import { + Individual, + Organization, + Role, +} from '@geonetwork-ui/common/domain/model/record' +import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' +import { UserModel } from '@geonetwork-ui/common/domain/model/user' +import { CommonModule } from '@angular/common' +import { TranslateModule } from '@ngx-translate/core' +import { ContactCardComponent } from '../../../contact-card/contact-card.component' +import { + AutocompleteComponent, + DropdownSelectorComponent, + UiInputsModule, +} from '@geonetwork-ui/ui/inputs' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { FormControl } from '@angular/forms' + +const organizationBarbie: Organization = { + name: 'Barbie Inc.', +} + +const organizationGoogle: Organization = { + name: 'Google', +} + +class MockPlatformServiceInterface { + getUsers = jest.fn(() => new BehaviorSubject([])) +} + +class MockOrganizationsServiceInterface { + organisations$ = new BehaviorSubject([organizationBarbie, organizationGoogle]) +} + +describe('FormFieldContactsForResourceComponent', () => { + let component: FormFieldContactsComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + FormFieldContactsComponent, + CommonModule, + TranslateModule.forRoot(), + UiInputsModule, + ContactCardComponent, + DropdownSelectorComponent, + ], + providers: [ + { + provide: PlatformServiceInterface, + useClass: MockPlatformServiceInterface, + }, + { + provide: OrganizationsServiceInterface, + useClass: MockOrganizationsServiceInterface, + }, + ChangeDetectorRef, + ], + }) + .overrideComponent(AutocompleteComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents() + + fixture = TestBed.createComponent(FormFieldContactsComponent) + component = fixture.componentInstance + component.control = new FormControl([]) + fixture.detectChanges() + }) + + it('should create the component', () => { + expect(component).toBeTruthy() + }) + + describe('ngOnInit', () => { + it('should initialize organizations', async () => { + await component.ngOnInit() + + expect(component.allOrganizations.size).toBe(2) + }) + }) + + describe('addRoleToDisplay', () => { + it('should add role to display and filter roles to pick', () => { + const initialRolesToPick = [...component.rolesToPick] + const roleToAdd = initialRolesToPick[0] + + component.addRoleToDisplay(roleToAdd) + + expect(component.roleSectionsToDisplay).toContain(roleToAdd) + expect(component.rolesToPick).not.toContain(roleToAdd) + }) + }) + + describe('filterRolesToPick', () => { + it('should filter roles already in roleSectionsToDisplay', () => { + component.rolesToPick = ['custodian', 'owner'] as Role[] + component.roleSectionsToDisplay = ['custodian'] as Role[] + + component.filterRolesToPick() + + expect(component.rolesToPick).toEqual(['owner']) + }) + }) + + describe('updateContactsForRessource', () => { + it('should update contactsForRessourceByRole and contactsAsDynElemByRole', () => { + const mockContact: Individual = { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual + + component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) + component.control.setValue([mockContact]) + + component.updateContacts() + + expect(component.contactsForRessourceByRole.get('owner')).toEqual([ + mockContact, + ]) + expect(component.contactsAsDynElemByRole.get('owner').length).toBe(1) + }) + }) + + describe('manageRoleSectionsToDisplay', () => { + it('should add new roles to roleSectionsToDisplay', () => { + const mockContact: Individual = { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual + + component.manageRoleSectionsToDisplay([mockContact]) + + expect(component.roleSectionsToDisplay).toContain('owner') + }) + }) + + describe('removeContact', () => { + it('should remove contact at specified index', () => { + const mockContacts: Individual[] = [ + { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual, + { + role: 'custodian', + organization: { name: 'Org2' } as Organization, + } as Individual, + ] + + component.control.setValue(mockContacts) + component.removeContact(0) + + expect(component.control.value.length).toBe(1) + expect(component.control.value[0]).toEqual(mockContacts[1]) + }) + }) + + describe('handleContactsChanged', () => { + it('should update contacts based on reordered dynamic elements', () => { + const mockContacts: Individual[] = [ + { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual, + { + role: 'owner', + organization: { name: 'Org2' } as Organization, + } as Individual, + ] + + component.contactsForRessourceByRole.set('owner', [mockContacts[0]]) + component.contactsForRessourceByRole.set('owner', [mockContacts[1]]) + + const reorderedElements = [ + { inputs: { contact: mockContacts[1] } } as any, + { inputs: { contact: mockContacts[0] } } as any, + ] + + component.handleContactsChanged(reorderedElements) + + const newControlValue = component.control.value + expect(newControlValue[0]).toEqual(mockContacts[1]) + expect(newControlValue[1]).toEqual(mockContacts[0]) + }) + }) + + describe('addContact', () => { + it('should add a new contact to the control value', () => { + const mockUser: UserModel = { + username: 'user1', + name: 'John', + surname: 'Doe', + organisation: 'Org1', + } as UserModel + + component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) + const initialContacts = component.control.value.length + + component.addContact(mockUser, 'owner') + + expect(component.control.value.length).toBe(initialContacts + 1) + expect(component.control.value[initialContacts].role).toBe('owner') + expect(component.control.value[initialContacts].organization.name).toBe( + 'Org1' + ) + }) + }) + + describe('ngOnDestroy', () => { + it('should unsubscribe from all subscriptions', () => { + const subscriptionSpy = jest.spyOn(component.subscription, 'unsubscribe') + + component.ngOnDestroy() + + expect(subscriptionSpy).toHaveBeenCalled() + }) + }) +}) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts new file mode 100644 index 0000000000..371379b611 --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts @@ -0,0 +1,217 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from '@angular/core' +import { FormControl } from '@angular/forms' +import { + AutocompleteComponent, + DropdownSelectorComponent, + UiInputsModule, +} from '@geonetwork-ui/ui/inputs' +import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' +import { + Individual, + Organization, + Role, +} from '@geonetwork-ui/common/domain/model/record' +import { TranslateModule } from '@ngx-translate/core' +import { + debounceTime, + distinctUntilChanged, + firstValueFrom, + Observable, + Subscription, + switchMap, +} from 'rxjs' +import { UserModel } from '@geonetwork-ui/common/domain/model/user' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { ContactCardComponent } from '../../../contact-card/contact-card.component' +import { + DynamicElement, + SortableListComponent, +} from '@geonetwork-ui/ui/elements' +import { createFuzzyFilter } from '@geonetwork-ui/util/shared' +import { map } from 'rxjs/operators' + +@Component({ + selector: 'gn-ui-form-field-contacts', + templateUrl: './form-field-contacts.component.html', + styleUrls: ['./form-field-contacts.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + DropdownSelectorComponent, + UiInputsModule, + CommonModule, + UiWidgetsModule, + AutocompleteComponent, + TranslateModule, + ContactCardComponent, + SortableListComponent, + ], +}) +export class FormFieldContactsComponent + implements OnInit, OnDestroy, OnChanges +{ + @Input() control: FormControl + + contacts: Individual[] = [] + contactsAsDynElem: DynamicElement[] = [] + + subscription: Subscription = new Subscription() + + allUsers$: Observable + + rolesToPick: Role[] = ['point_of_contact'] + + allOrganizations: Map = new Map() + + constructor( + private platformServiceInterface: PlatformServiceInterface, + private organizationsServiceInterface: OrganizationsServiceInterface, + private changeDetectorRef: ChangeDetectorRef + ) { + this.allUsers$ = this.platformServiceInterface.getUsers() + } + + ngOnChanges(changes: SimpleChanges): void { + console.log(changes['control']) + } + + async ngOnInit(): Promise { + this.allOrganizations = new Map( + ( + await firstValueFrom(this.organizationsServiceInterface.organisations$) + ).map((organization) => [organization.name, organization]) + ) + + this.updateContacts() + + this.changeDetectorRef.markForCheck() + + this.subscription.add( + this.control.valueChanges.subscribe((contacts) => { + console.log('new contacts (valueChange): ', contacts) + this.updateContacts() + this.changeDetectorRef.markForCheck() + }) + ) + } + + updateContacts() { + this.contacts = this.control.value.reduce((acc, contact) => { + const completeOrganization = this.allOrganizations.get( + contact.organization.name + ) + + const updatedContact = { + ...contact, + organization: + completeOrganization ?? + ({ name: contact.organization.name } as Organization), + } + + acc.push(updatedContact) + + return acc + }, [] as Individual[]) + + this.contactsAsDynElem = this.control.value.reduce((acc, contact) => { + const completeOrganization = this.allOrganizations.get( + contact.organization.name + ) + + const updatedContact = { + ...contact, + organization: + completeOrganization ?? + ({ name: contact.organization.name } as Organization), + } + + const contactAsDynElem = { + component: ContactCardComponent, + inputs: { + contact: updatedContact, + removable: false, + }, + } as DynamicElement + + acc.push(contactAsDynElem) + + return acc + }, [] as DynamicElement[]) + + this.changeDetectorRef.markForCheck() + } + + removeContact() { + this.control.setValue([]) + } + + handleContactsChanged(event: DynamicElement[]) { + const newContactsOrdered = event.map( + (contactAsDynElem) => contactAsDynElem.inputs['contact'] + ) as Individual[] + + console.log('newContactsOrdered :', newContactsOrdered) + + this.control.setValue(newContactsOrdered) + } + + /** + * gn-ui-autocomplete + */ + displayWithFn: (user: UserModel) => string = (user) => + `${user.name} ${user.surname} ${ + user.organisation ? `(${user.organisation})` : '' + }` + + /** + * gn-ui-autocomplete + */ + autoCompleteAction = (query: string) => { + const fuzzyFilter = createFuzzyFilter(query) + return this.allUsers$.pipe( + switchMap((users) => [ + users.filter((user) => fuzzyFilter(user.username)), + ]), + map((results) => results.slice(0, 10)), + debounceTime(300), + distinctUntilChanged() + ) + } + + /** + * gn-ui-autocomplete + */ + addContact(contact: UserModel) { + const newContacts = { + firstName: contact.name ?? '', + lastName: contact.surname ?? '', + organization: + this.allOrganizations.get(contact.organisation) ?? + ({ name: contact.organisation } as Organization), + email: contact.email ?? '', + role: 'point_of_contact', + address: '', + phone: '', + position: '', + } as Individual + + const newControlValue = [...this.control.value, newContacts] + + this.control.setValue(newControlValue) + } + + ngOnDestroy(): void { + this.subscription.unsubscribe() + } +} diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html index ea903d38fb..00e4f87667 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html @@ -123,4 +123,9 @@ > + + + diff --git a/libs/feature/editor/src/lib/fields.config.ts b/libs/feature/editor/src/lib/fields.config.ts index 0b94d73a46..5574c24ff0 100644 --- a/libs/feature/editor/src/lib/fields.config.ts +++ b/libs/feature/editor/src/lib/fields.config.ts @@ -93,6 +93,13 @@ export const CONTACTS_FOR_RESOURCE_FIELD: EditorField = { }, } +export const CONTACTS: EditorField = { + model: 'contacts', + formFieldConfig: { + labelKey: '', + }, +} + export const RECORD_GRAPHICAL_OVERVIEW_FIELD: EditorField = { model: 'overviews', formFieldConfig: { @@ -191,7 +198,7 @@ export const DATA_POINT_OF_CONTACT_SECTION: EditorSection = { 'editor.record.form.section.dataPointOfContact.description' ), hidden: false, - fields: [], + fields: [CONTACTS], } /************************************************************ From deed6da4c4fccf96d67c44e5da31ff83e24ea33e Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Thu, 29 Aug 2024 10:39:04 +0200 Subject: [PATCH 2/7] wip --- .../form-field-contacts.component.html | 38 +---------- .../form-field-contacts.component.ts | 64 ++++++++----------- .../form-field/form-field.component.html | 11 ++-- translations/de.json | 1 + translations/en.json | 1 + translations/es.json | 1 + translations/fr.json | 1 + translations/it.json | 1 + translations/nl.json | 1 + translations/pt.json | 1 + translations/sk.json | 1 + 11 files changed, 42 insertions(+), 79 deletions(-) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html index 86db0afdf0..5bf1b7c49a 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html @@ -1,40 +1,4 @@
-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts index 371379b611..ed75b78c66 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts @@ -3,13 +3,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + EventEmitter, Input, OnChanges, OnDestroy, - OnInit, + Output, SimpleChanges, } from '@angular/core' -import { FormControl } from '@angular/forms' import { AutocompleteComponent, DropdownSelectorComponent, @@ -58,10 +58,9 @@ import { map } from 'rxjs/operators' SortableListComponent, ], }) -export class FormFieldContactsComponent - implements OnInit, OnDestroy, OnChanges -{ - @Input() control: FormControl +export class FormFieldContactsComponent implements OnDestroy, OnChanges { + @Input() value: Individual[] + @Output() valueChange: EventEmitter = new EventEmitter() contacts: Individual[] = [] contactsAsDynElem: DynamicElement[] = [] @@ -82,32 +81,31 @@ export class FormFieldContactsComponent this.allUsers$ = this.platformServiceInterface.getUsers() } - ngOnChanges(changes: SimpleChanges): void { - console.log(changes['control']) - } + async ngOnChanges(changes: SimpleChanges): Promise { + const contactsChanges = changes['value'] - async ngOnInit(): Promise { - this.allOrganizations = new Map( - ( - await firstValueFrom(this.organizationsServiceInterface.organisations$) - ).map((organization) => [organization.name, organization]) - ) + if (contactsChanges.firstChange) { + this.allOrganizations = new Map( + ( + await firstValueFrom( + this.organizationsServiceInterface.organisations$ + ) + ).map((organization) => [organization.name, organization]) + ) + } - this.updateContacts() + if (contactsChanges.currentValue !== contactsChanges.previousValue) { + const contacts = contactsChanges.currentValue + console.log(contacts) - this.changeDetectorRef.markForCheck() + this.updateContacts() - this.subscription.add( - this.control.valueChanges.subscribe((contacts) => { - console.log('new contacts (valueChange): ', contacts) - this.updateContacts() - this.changeDetectorRef.markForCheck() - }) - ) + this.changeDetectorRef.markForCheck() + } } updateContacts() { - this.contacts = this.control.value.reduce((acc, contact) => { + this.contacts = this.value.reduce((acc, contact) => { const completeOrganization = this.allOrganizations.get( contact.organization.name ) @@ -124,7 +122,7 @@ export class FormFieldContactsComponent return acc }, [] as Individual[]) - this.contactsAsDynElem = this.control.value.reduce((acc, contact) => { + this.contactsAsDynElem = this.value.reduce((acc, contact) => { const completeOrganization = this.allOrganizations.get( contact.organization.name ) @@ -152,18 +150,12 @@ export class FormFieldContactsComponent this.changeDetectorRef.markForCheck() } - removeContact() { - this.control.setValue([]) - } - handleContactsChanged(event: DynamicElement[]) { const newContactsOrdered = event.map( (contactAsDynElem) => contactAsDynElem.inputs['contact'] ) as Individual[] - console.log('newContactsOrdered :', newContactsOrdered) - - this.control.setValue(newContactsOrdered) + this.valueChange.emit(newContactsOrdered) } /** @@ -193,7 +185,7 @@ export class FormFieldContactsComponent * gn-ui-autocomplete */ addContact(contact: UserModel) { - const newContacts = { + const newContact = { firstName: contact.name ?? '', lastName: contact.surname ?? '', organization: @@ -206,9 +198,7 @@ export class FormFieldContactsComponent position: '', } as Individual - const newControlValue = [...this.control.value, newContacts] - - this.control.setValue(newControlValue) + this.valueChange.emit([...this.value, newContact]) } ngOnDestroy(): void { diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html index 00e4f87667..a303aa2c93 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html @@ -122,10 +122,11 @@ (valueChange)="valueChange.emit($event)" > - - - + + + diff --git a/translations/de.json b/translations/de.json index 24b74e605d..38b8ea9523 100644 --- a/translations/de.json +++ b/translations/de.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "Kurzbeschreibung", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "Schlagwörter", "editor.record.form.field.license": "Lizenz", diff --git a/translations/en.json b/translations/en.json index ce7750795b..57643c9fd1 100644 --- a/translations/en.json +++ b/translations/en.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "Previous", "editor.record.form.classification.opendata": "Open Data", "editor.record.form.field.abstract": "Abstract", + "editor.record.form.field.contacts.noContact": "Please provide at least one point of contact.", "editor.record.form.field.contactsForResource.noContact": "Please provide at least one point of contact responsible for the data.", "editor.record.form.field.keywords": "Keywords", "editor.record.form.field.license": "License", diff --git a/translations/es.json b/translations/es.json index e708c32ec0..6f87aa7c9d 100644 --- a/translations/es.json +++ b/translations/es.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "", "editor.record.form.field.license": "", diff --git a/translations/fr.json b/translations/fr.json index 42a4944787..9062405511 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "Précédent", "editor.record.form.classification.opendata": "Données ouvertes", "editor.record.form.field.abstract": "Résumé", + "editor.record.form.field.contacts.noContact": "Veuillez renseigner au moins un point de contact.", "editor.record.form.field.contactsForResource.noContact": "Veuillez renseigner au moins un point de contact responsable de la donnée.", "editor.record.form.field.keywords": "Mots-clés", "editor.record.form.field.license": "Licence", diff --git a/translations/it.json b/translations/it.json index 1f87f83ac5..6508983d02 100644 --- a/translations/it.json +++ b/translations/it.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "", "editor.record.form.field.license": "Licenza", diff --git a/translations/nl.json b/translations/nl.json index c5895c20c4..d6ab9c4d22 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "", "editor.record.form.field.license": "", diff --git a/translations/pt.json b/translations/pt.json index 64c5b7975d..58f0dd7185 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "", "editor.record.form.field.license": "", diff --git a/translations/sk.json b/translations/sk.json index 3911e28e0e..c71d528ab3 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -206,6 +206,7 @@ "editor.record.form.bottomButtons.previous": "", "editor.record.form.classification.opendata": "", "editor.record.form.field.abstract": "", + "editor.record.form.field.contacts.noContact": "", "editor.record.form.field.contactsForResource.noContact": "", "editor.record.form.field.keywords": "", "editor.record.form.field.license": "Licencia", From 770f3cae04d44bbd993fe70d9530cdf2ff955607 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 23 Sep 2024 09:56:32 +0200 Subject: [PATCH 3/7] fix after rebase --- .../form-field-contacts.component.html | 5 +- .../form-field-contacts.component.ts | 46 ++++--------------- .../form-field/form-field.component.ts | 4 +- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html index 5bf1b7c49a..689c201feb 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html @@ -13,14 +13,13 @@ diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts index ed75b78c66..7972363553 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts @@ -34,12 +34,9 @@ import { UserModel } from '@geonetwork-ui/common/domain/model/user' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { ContactCardComponent } from '../../../contact-card/contact-card.component' -import { - DynamicElement, - SortableListComponent, -} from '@geonetwork-ui/ui/elements' import { createFuzzyFilter } from '@geonetwork-ui/util/shared' import { map } from 'rxjs/operators' +import { SortableListComponent } from '@geonetwork-ui/ui/layout' @Component({ selector: 'gn-ui-form-field-contacts', @@ -63,7 +60,6 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { @Output() valueChange: EventEmitter = new EventEmitter() contacts: Individual[] = [] - contactsAsDynElem: DynamicElement[] = [] subscription: Subscription = new Subscription() @@ -84,6 +80,8 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { async ngOnChanges(changes: SimpleChanges): Promise { const contactsChanges = changes['value'] + console.log(contactsChanges) + if (contactsChanges.firstChange) { this.allOrganizations = new Map( ( @@ -121,41 +119,15 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { return acc }, [] as Individual[]) - - this.contactsAsDynElem = this.value.reduce((acc, contact) => { - const completeOrganization = this.allOrganizations.get( - contact.organization.name - ) - - const updatedContact = { - ...contact, - organization: - completeOrganization ?? - ({ name: contact.organization.name } as Organization), - } - - const contactAsDynElem = { - component: ContactCardComponent, - inputs: { - contact: updatedContact, - removable: false, - }, - } as DynamicElement - - acc.push(contactAsDynElem) - - return acc - }, [] as DynamicElement[]) - - this.changeDetectorRef.markForCheck() } - handleContactsChanged(event: DynamicElement[]) { - const newContactsOrdered = event.map( - (contactAsDynElem) => contactAsDynElem.inputs['contact'] - ) as Individual[] + handleContactsChanged(items: unknown[]) { + console.log(items) + // const newContactsOrdered = items.map( + // (contactAsDynElem) => contactAsDynElem.inputs['contact'] + // ) as Individual[] - this.valueChange.emit(newContactsOrdered) + // this.valueChange.emit(newContactsOrdered) } /** diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts index 258b8c3502..9d27053087 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts @@ -24,8 +24,8 @@ import { EditableLabelDirective } from '@geonetwork-ui/ui/inputs' import { FormFieldWrapperComponent } from '@geonetwork-ui/ui/layout' import { TranslateModule } from '@ngx-translate/core' import { - FormFieldLicenseComponent, FormFieldDateUpdatedComponent, + FormFieldLicenseComponent, FormFieldTemporalExtentsComponent, } from '.' import { FieldModelSpecifier, FormFieldConfig } from '../../../models' @@ -41,6 +41,7 @@ import { FormFieldSpatialExtentComponent } from './form-field-spatial-extent/for import { FormFieldUpdateFrequencyComponent } from './form-field-update-frequency/form-field-update-frequency.component' import { FormFieldOpenDataComponent } from './form-field-open-data/form-field-open-data.component' import { FormFieldOnlineLinkResourcesComponent } from './form-field-online-link-resources/form-field-online-link-resources.component' +import { FormFieldContactsComponent } from './form-field-contacts/form-field-contacts.component' @Component({ selector: 'gn-ui-form-field', @@ -70,6 +71,7 @@ import { FormFieldOnlineLinkResourcesComponent } from './form-field-online-link- FormFieldContactsForResourceComponent, FormFieldOpenDataComponent, FormFieldOnlineLinkResourcesComponent, + FormFieldContactsComponent, ], }) export class FormFieldComponent { From 0b997c1677c37b74311040627ee6417c9bfc8d83 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 23 Sep 2024 13:18:01 +0200 Subject: [PATCH 4/7] fix after rebase 2 --- .../src/lib/iso19115-3/read-parts.ts | 1 + .../src/lib/iso19139/read-parts.ts | 2 ++ .../src/lib/iso19139/write-parts.ts | 2 ++ .../form-field-contacts.component.html | 6 ++++++ .../form-field-contacts.component.ts | 14 ++++---------- .../form-field/form-field.component.html | 12 ++++++------ 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts b/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts index e06bf7b20b..caca9ebb73 100644 --- a/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts @@ -229,6 +229,7 @@ export function readOwnerOrganization(rootEl: XmlElement): Organization { } export function readContacts(rootEl: XmlElement): Individual[] { + console.log('ici') return pipe( findNestedElements('mdb:contact', 'cit:CI_Responsibility'), mapArray(extractIndividuals()), diff --git a/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts b/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts index 34dee9e287..ebcd72e6ba 100644 --- a/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts @@ -600,6 +600,7 @@ export function readAbstract(rootEl: XmlElement): string { } export function readContacts(rootEl: XmlElement): Individual[] { + console.log('converter : readContacts') return pipe( findChildrenElement('gmd:contact', false), mapArray(findChildElement('gmd:CI_ResponsibleParty', false)), @@ -608,6 +609,7 @@ export function readContacts(rootEl: XmlElement): Individual[] { } export function readContactsForResource(rootEl: XmlElement): Individual[] { + console.log('converter : readContactsForResource') return pipe( findIdentification(), combine( diff --git a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts index 8471232292..38ec9f59b4 100644 --- a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts @@ -782,6 +782,7 @@ export function writeStatus(record: DatasetRecord, rootEl: XmlElement) { } export function writeContacts(record: CatalogRecord, rootEl: XmlElement) { + console.log('converter : writeContacts') pipe( removeChildrenByName('gmd:contact'), appendChildren( @@ -796,6 +797,7 @@ export function writeContactsForResource( record: CatalogRecord, rootEl: XmlElement ) { + console.log('converter : writeContactsForResource') pipe( findOrCreateIdentification(), removeChildrenByName('gmd:pointOfContact'), diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html index 689c201feb..822ed4b9fb 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html @@ -8,6 +8,7 @@ [clearOnSelection]="true" > + @@ -20,10 +21,15 @@ + + +
+
{ const contactsChanges = changes['value'] - console.log(contactsChanges) - if (contactsChanges.firstChange) { this.allOrganizations = new Map( ( @@ -93,9 +91,6 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { } if (contactsChanges.currentValue !== contactsChanges.previousValue) { - const contacts = contactsChanges.currentValue - console.log(contacts) - this.updateContacts() this.changeDetectorRef.markForCheck() @@ -122,12 +117,11 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { } handleContactsChanged(items: unknown[]) { - console.log(items) - // const newContactsOrdered = items.map( - // (contactAsDynElem) => contactAsDynElem.inputs['contact'] - // ) as Individual[] + const contacts = items as Individual[] + + this.contacts = contacts - // this.valueChange.emit(newContactsOrdered) + this.valueChange.emit(contacts) } /** diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html index a303aa2c93..d48c8df844 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html @@ -114,6 +114,12 @@ (valueChange)="valueChange.emit($event)" > + + + - - - From f545bc44a04a14267509fca56365518e54b6b4f6 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 23 Sep 2024 16:34:30 +0200 Subject: [PATCH 5/7] fix unit tests --- .../src/lib/iso19115-3/read-parts.ts | 1 - .../src/lib/iso19139/read-parts.ts | 2 - .../src/lib/iso19139/write-parts.ts | 2 - libs/common/fixtures/src/index.ts | 1 + .../fixtures/src/lib/individual.fixtures.ts | 61 ++++ libs/common/fixtures/src/lib/user.fixtures.ts | 13 + .../editor/src/lib/+state/editor.reducer.ts | 2 +- .../form-field-contacts.component.spec.ts | 285 ++++++++---------- .../form-field-contacts.component.ts | 3 - 9 files changed, 203 insertions(+), 167 deletions(-) create mode 100644 libs/common/fixtures/src/lib/individual.fixtures.ts diff --git a/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts b/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts index caca9ebb73..e06bf7b20b 100644 --- a/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts @@ -229,7 +229,6 @@ export function readOwnerOrganization(rootEl: XmlElement): Organization { } export function readContacts(rootEl: XmlElement): Individual[] { - console.log('ici') return pipe( findNestedElements('mdb:contact', 'cit:CI_Responsibility'), mapArray(extractIndividuals()), diff --git a/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts b/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts index ebcd72e6ba..34dee9e287 100644 --- a/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts @@ -600,7 +600,6 @@ export function readAbstract(rootEl: XmlElement): string { } export function readContacts(rootEl: XmlElement): Individual[] { - console.log('converter : readContacts') return pipe( findChildrenElement('gmd:contact', false), mapArray(findChildElement('gmd:CI_ResponsibleParty', false)), @@ -609,7 +608,6 @@ export function readContacts(rootEl: XmlElement): Individual[] { } export function readContactsForResource(rootEl: XmlElement): Individual[] { - console.log('converter : readContactsForResource') return pipe( findIdentification(), combine( diff --git a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts index 38ec9f59b4..8471232292 100644 --- a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +++ b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts @@ -782,7 +782,6 @@ export function writeStatus(record: DatasetRecord, rootEl: XmlElement) { } export function writeContacts(record: CatalogRecord, rootEl: XmlElement) { - console.log('converter : writeContacts') pipe( removeChildrenByName('gmd:contact'), appendChildren( @@ -797,7 +796,6 @@ export function writeContactsForResource( record: CatalogRecord, rootEl: XmlElement ) { - console.log('converter : writeContactsForResource') pipe( findOrCreateIdentification(), removeChildrenByName('gmd:pointOfContact'), diff --git a/libs/common/fixtures/src/index.ts b/libs/common/fixtures/src/index.ts index 71987343f8..232576ae5d 100644 --- a/libs/common/fixtures/src/index.ts +++ b/libs/common/fixtures/src/index.ts @@ -10,6 +10,7 @@ export * from './lib/record-link.fixtures' export * from './lib/records.fixtures' export * from './lib/repository.fixtures' export * from './lib/user.fixtures' +export * from './lib/individual.fixtures' export * from './lib/user-feedbacks.fixtures' export * from './lib/editor' diff --git a/libs/common/fixtures/src/lib/individual.fixtures.ts b/libs/common/fixtures/src/lib/individual.fixtures.ts new file mode 100644 index 0000000000..149281d954 --- /dev/null +++ b/libs/common/fixtures/src/lib/individual.fixtures.ts @@ -0,0 +1,61 @@ +import { Individual } from '@geonetwork-ui/common/domain/model/record' +import { barbieIncOrganizationFixture } from './organisations.fixture' + +export const createIndividualFixture = ( + overrides: Partial = {} +): Individual => ({ + firstName: 'Arnaud', + lastName: 'Demaison', + email: 'a.demaison@geo2france.fr', + organization: barbieIncOrganizationFixture(), + role: 'point_of_contact', + ...overrides, +}) + +export const barbieIndividualFixture = (): Individual => + createIndividualFixture({ + firstName: 'Barbara', + lastName: 'Roberts', + email: 'barbie@email.org', + organization: barbieIncOrganizationFixture(), + role: 'point_of_contact', + }) + +export const someIndividualsFixture = (): Individual[] => [ + barbieIndividualFixture(), + { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@email.com', + organization: barbieIncOrganizationFixture(), + role: 'owner', + }, + { + firstName: 'Alice', + lastName: 'Smith', + email: 'alice.smith@workplace.com', + organization: barbieIncOrganizationFixture(), + role: 'author', + }, + { + firstName: 'Michael', + lastName: 'Johnson', + email: 'michael.j@company.org', + organization: barbieIncOrganizationFixture(), + role: 'contributor', + }, + { + firstName: 'Emma', + lastName: 'Williams', + email: 'emma.w@business.io', + organization: barbieIncOrganizationFixture(), + role: 'rights_holder', + }, + { + firstName: 'David', + lastName: 'Brown', + email: 'david.brown@enterprise.net', + organization: barbieIncOrganizationFixture(), + role: 'stakeholder', + }, +] diff --git a/libs/common/fixtures/src/lib/user.fixtures.ts b/libs/common/fixtures/src/lib/user.fixtures.ts index b6399d0476..5d110f9835 100644 --- a/libs/common/fixtures/src/lib/user.fixtures.ts +++ b/libs/common/fixtures/src/lib/user.fixtures.ts @@ -28,6 +28,19 @@ export const barbieUserFixture = (): UserModel => 'https://www.gravatar.com/avatar/dbdffd183622800bcf8587328daf43a6?d=mp', }) +export const johnDoeUserFixture = (): UserModel => + createUserFixture({ + id: '12345', + profile: 'User', + username: 'johndoe', + name: 'John', + surname: 'Doe', + email: 'johndoe@email.com', + organisation: 'Doe Enterprises', + profileIcon: + 'https://www.gravatar.com/avatar/5f6d74eabcb57186a12f7c8ba40b4c9f?d=mp', + }) + export const ghostUserFixture = (): UserModel => createUserFixture({ id: '161', diff --git a/libs/feature/editor/src/lib/+state/editor.reducer.ts b/libs/feature/editor/src/lib/+state/editor.reducer.ts index dfb4835ae5..d2b4e96a5c 100644 --- a/libs/feature/editor/src/lib/+state/editor.reducer.ts +++ b/libs/feature/editor/src/lib/+state/editor.reducer.ts @@ -38,7 +38,7 @@ export const initialEditorState: EditorState = { saveError: null, changedSinceSave: false, editorConfig: DEFAULT_CONFIGURATION, - currentPage: 2, //todo: remove before merge + currentPage: 0, } const reducer = createReducer( diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts index 0f21913bb4..9bd44bc4e2 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.spec.ts @@ -1,75 +1,71 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { FormFieldContactsComponent } from './form-field-contacts.component' +import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' -import { BehaviorSubject } from 'rxjs' +import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' +import { BehaviorSubject, of } from 'rxjs' import { Individual, Organization, - Role, } from '@geonetwork-ui/common/domain/model/record' -import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' -import { UserModel } from '@geonetwork-ui/common/domain/model/user' -import { CommonModule } from '@angular/common' +import { MockBuilder, MockInstance, MockProvider } from 'ng-mocks' import { TranslateModule } from '@ngx-translate/core' -import { ContactCardComponent } from '../../../contact-card/contact-card.component' import { - AutocompleteComponent, - DropdownSelectorComponent, - UiInputsModule, -} from '@geonetwork-ui/ui/inputs' -import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' -import { FormControl } from '@angular/forms' + barbieIncOrganizationFixture, + barbieUserFixture, + someIndividualsFixture, + someOrganizationsFixture, + someUsersFixture, +} from '@geonetwork-ui/common/fixtures' -const organizationBarbie: Organization = { - name: 'Barbie Inc.', -} +describe('FormFieldContactsComponent', () => { + MockInstance.scope() -const organizationGoogle: Organization = { - name: 'Google', -} - -class MockPlatformServiceInterface { - getUsers = jest.fn(() => new BehaviorSubject([])) -} - -class MockOrganizationsServiceInterface { - organisations$ = new BehaviorSubject([organizationBarbie, organizationGoogle]) -} - -describe('FormFieldContactsForResourceComponent', () => { let component: FormFieldContactsComponent let fixture: ComponentFixture + let platformServiceInterface: PlatformServiceInterface + let organizationsServiceInterface: OrganizationsServiceInterface + let changeDetectorRef: ChangeDetectorRef - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - FormFieldContactsComponent, - CommonModule, - TranslateModule.forRoot(), - UiInputsModule, - ContactCardComponent, - DropdownSelectorComponent, - ], + beforeEach(() => { + return MockBuilder(FormFieldContactsComponent) + }) + + const mockUsers = someUsersFixture() + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [FormFieldContactsComponent, TranslateModule.forRoot()], providers: [ - { - provide: PlatformServiceInterface, - useClass: MockPlatformServiceInterface, - }, - { - provide: OrganizationsServiceInterface, - useClass: MockOrganizationsServiceInterface, - }, - ChangeDetectorRef, + MockProvider(OrganizationsServiceInterface), + MockProvider(PlatformServiceInterface, { + getUsers: jest.fn().mockReturnValue(of(mockUsers)), + }), + MockProvider(ChangeDetectorRef, { + markForCheck: jest.fn().mockReturnValue({}), + }), ], + }).overrideComponent(FormFieldContactsComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + }, }) - .overrideComponent(AutocompleteComponent, { - set: { changeDetection: ChangeDetectionStrategy.Default }, - }) - .compileComponents() fixture = TestBed.createComponent(FormFieldContactsComponent) component = fixture.componentInstance - component.control = new FormControl([]) + + changeDetectorRef = TestBed.inject(ChangeDetectorRef) + platformServiceInterface = TestBed.inject(PlatformServiceInterface) + organizationsServiceInterface = TestBed.inject( + OrganizationsServiceInterface + ) + + organizationsServiceInterface.organisations$ = new BehaviorSubject( + someOrganizationsFixture() + ) + + component.value = [] + fixture.detectChanges() }) @@ -77,148 +73,121 @@ describe('FormFieldContactsForResourceComponent', () => { expect(component).toBeTruthy() }) - describe('ngOnInit', () => { - it('should initialize organizations', async () => { - await component.ngOnInit() + describe('ngOnChanges', () => { + it('should initialize allOrganizations on first change', async () => { + const orgs: Organization[] = [{ name: 'Org1' }, { name: 'Org2' }] + organizationsServiceInterface.organisations$ = of(orgs) + + component.value = [] + await component.ngOnChanges({ + value: { + currentValue: [], + previousValue: undefined, + firstChange: true, + isFirstChange: () => true, + }, + }) expect(component.allOrganizations.size).toBe(2) + expect(component.allOrganizations.get('Org1')).toEqual({ name: 'Org1' }) + expect(component.allOrganizations.get('Org2')).toEqual({ name: 'Org2' }) }) - }) - - describe('addRoleToDisplay', () => { - it('should add role to display and filter roles to pick', () => { - const initialRolesToPick = [...component.rolesToPick] - const roleToAdd = initialRolesToPick[0] - - component.addRoleToDisplay(roleToAdd) - - expect(component.roleSectionsToDisplay).toContain(roleToAdd) - expect(component.rolesToPick).not.toContain(roleToAdd) - }) - }) - - describe('filterRolesToPick', () => { - it('should filter roles already in roleSectionsToDisplay', () => { - component.rolesToPick = ['custodian', 'owner'] as Role[] - component.roleSectionsToDisplay = ['custodian'] as Role[] - - component.filterRolesToPick() - - expect(component.rolesToPick).toEqual(['owner']) - }) - }) - - describe('updateContactsForRessource', () => { - it('should update contactsForRessourceByRole and contactsAsDynElemByRole', () => { - const mockContact: Individual = { - role: 'owner', - organization: { name: 'Org1' } as Organization, - } as Individual - component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) - component.control.setValue([mockContact]) + it('should update contacts and mark for check when value changes', async () => { + jest.spyOn(component, 'updateContacts') - component.updateContacts() + component.value = [] + await component.ngOnChanges({ + value: { + currentValue: [], + previousValue: null, + firstChange: false, + isFirstChange: () => false, + }, + }) - expect(component.contactsForRessourceByRole.get('owner')).toEqual([ - mockContact, - ]) - expect(component.contactsAsDynElemByRole.get('owner').length).toBe(1) + expect(component.updateContacts).toHaveBeenCalled() }) }) - describe('manageRoleSectionsToDisplay', () => { - it('should add new roles to roleSectionsToDisplay', () => { - const mockContact: Individual = { - role: 'owner', - organization: { name: 'Org1' } as Organization, - } as Individual - - component.manageRoleSectionsToDisplay([mockContact]) - - expect(component.roleSectionsToDisplay).toContain('owner') + describe('updateContacts', () => { + beforeEach(async () => { + await component.ngOnChanges({ + value: { + currentValue: [], + previousValue: undefined, + firstChange: true, + isFirstChange: () => true, + }, + }) }) - }) - describe('removeContact', () => { - it('should remove contact at specified index', () => { - const mockContacts: Individual[] = [ + it('should update contacts with complete organization data', () => { + component.value = [ { - role: 'owner', - organization: { name: 'Org1' } as Organization, - } as Individual, + ...someIndividualsFixture()[0], + organization: { name: barbieIncOrganizationFixture().name }, + }, { - role: 'custodian', - organization: { name: 'Org2' } as Organization, - } as Individual, + ...someIndividualsFixture()[1], + organization: { name: barbieIncOrganizationFixture().name }, + }, ] - component.control.setValue(mockContacts) - component.removeContact(0) + component.updateContacts() - expect(component.control.value.length).toBe(1) - expect(component.control.value[0]).toEqual(mockContacts[1]) + expect(component.contacts.length).toBe(2) + expect(component.contacts[0].organization).toEqual( + barbieIncOrganizationFixture() + ) + expect(component.contacts[1].organization).toEqual( + barbieIncOrganizationFixture() + ) }) }) describe('handleContactsChanged', () => { - it('should update contacts based on reordered dynamic elements', () => { - const mockContacts: Individual[] = [ - { - role: 'owner', - organization: { name: 'Org1' } as Organization, - } as Individual, - { - role: 'owner', - organization: { name: 'Org2' } as Organization, - } as Individual, - ] + it('should update contacts and emit valueChange', () => { + const contacts: Individual[] = someIndividualsFixture() + jest.spyOn(component.valueChange, 'emit') - component.contactsForRessourceByRole.set('owner', [mockContacts[0]]) - component.contactsForRessourceByRole.set('owner', [mockContacts[1]]) + component.handleContactsChanged(contacts) - const reorderedElements = [ - { inputs: { contact: mockContacts[1] } } as any, - { inputs: { contact: mockContacts[0] } } as any, - ] - - component.handleContactsChanged(reorderedElements) - - const newControlValue = component.control.value - expect(newControlValue[0]).toEqual(mockContacts[1]) - expect(newControlValue[1]).toEqual(mockContacts[0]) + expect(component.contacts).toEqual(contacts) + expect(component.valueChange.emit).toHaveBeenCalledWith(contacts) }) }) describe('addContact', () => { - it('should add a new contact to the control value', () => { - const mockUser: UserModel = { - username: 'user1', - name: 'John', - surname: 'Doe', - organisation: 'Org1', - } as UserModel - + it('should add the contact and emit new contacts', () => { + const spy = jest.spyOn(component.valueChange, 'emit') + const mockUser = barbieUserFixture() component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) - const initialContacts = component.control.value.length - component.addContact(mockUser, 'owner') + component.addContact(mockUser) - expect(component.control.value.length).toBe(initialContacts + 1) - expect(component.control.value[initialContacts].role).toBe('owner') - expect(component.control.value[initialContacts].organization.name).toBe( - 'Org1' - ) + expect(spy).toHaveBeenCalledWith([ + { + address: '', + email: 'barbie@email.org', + firstName: 'Barbara', + lastName: 'Roberts', + organization: { name: 'Barbie Inc.' } as Organization, + phone: '', + position: '', + role: 'point_of_contact', + }, + ]) }) }) describe('ngOnDestroy', () => { - it('should unsubscribe from all subscriptions', () => { - const subscriptionSpy = jest.spyOn(component.subscription, 'unsubscribe') + it('should unsubscribe from subscriptions', () => { + jest.spyOn(component.subscription, 'unsubscribe') component.ngOnDestroy() - expect(subscriptionSpy).toHaveBeenCalled() + expect(component.subscription.unsubscribe).toHaveBeenCalled() }) }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts index 3261c014a5..909557f721 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts @@ -19,7 +19,6 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { Individual, Organization, - Role, } from '@geonetwork-ui/common/domain/model/record' import { TranslateModule } from '@ngx-translate/core' import { @@ -65,8 +64,6 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges { allUsers$: Observable - rolesToPick: Role[] = ['point_of_contact'] - allOrganizations: Map = new Map() constructor( From df28ed3d7c03df74ea480b93180a58bc37ce7d09 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 25 Sep 2024 17:14:36 +0200 Subject: [PATCH 6/7] fix(me): correct typos and grammatical errors in the French translation file --- translations/fr.json | 134 +++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/translations/fr.json b/translations/fr.json index 9062405511..9cd20a7b16 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -18,7 +18,7 @@ "chart.type.line": "ligne", "chart.type.lineSmooth": "ligne lisse", "chart.type.pie": "camembert", - "dashboard.catalog.allRecords": "Fiches de métadonnée", + "dashboard.catalog.allRecords": "Fiches de métadonnées", "dashboard.catalog.calendar": "Calendrier", "dashboard.catalog.contacts": "Annuaire", "dashboard.catalog.discussion": "Discussions", @@ -27,7 +27,7 @@ "dashboard.importRecord": "Importer", "dashboard.importRecord.importExternal": "Importer une fiche externe", "dashboard.importRecord.importExternalLabel": "URL de la fiche externe", - "dashboard.importRecord.useModel": "Utiliser un modele", + "dashboard.importRecord.useModel": "Utiliser un modèle", "dashboard.labels.catalog": "Catalogue", "dashboard.labels.mySpace": "Mon espace", "dashboard.records.all": "Catalogue", @@ -46,22 +46,22 @@ "dashboard.results.listResources": "Afficher les ressources", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "Détection du \n format de fichier", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Récupération des informations \n sur le jeu de données", - "datafeeder.analysisProgressBar.illustration.samplingData": "Sampling \n des données", - "datafeeder.analysisProgressBar.subtitle": "L'analyse peut prendre plusieurs minutes, merci d'attendre.", + "datafeeder.analysisProgressBar.illustration.samplingData": "Échantillonnage \n des données", + "datafeeder.analysisProgressBar.subtitle": "L'analyse peut prendre plusieurs minutes, merci de patienter.", "datafeeder.analysisProgressBar.title": "Analyse en cours", "datafeeder.datasetValidation.datasetInformation": "Le jeu de données fourni contient {number} entités", "datafeeder.datasetValidation.submitButton": "OK, mes données sont correctes", "datafeeder.datasetValidation.title": "Vérifiez que vos données sont correctes", "datafeeder.datasetValidation.unknown": " - ", - "datafeeder.datasetValidationCsv.explicitLineNumbers": "*Le tableau doit afficher les 5 premières lignes (hors en-tête)
Si ce n'est pas le cas, vérifier que le fichier est bien formatté", + "datafeeder.datasetValidationCsv.explicitLineNumbers": "*Le tableau doit afficher les 5 premières lignes (hors en-tête)
Si ce n'est pas le cas, vérifiez que le fichier est bien formaté", "datafeeder.datasetValidationCsv.lineNumbers": "Résumé des 5 premières lignes* du CSV :", "datafeeder.form.abstract": "Comment décrire votre jeu de données ?", "datafeeder.form.datepicker": "Savez-vous quand la donnée a été créée ?", "datafeeder.form.description": "Enfin, décrivez le processus utilisé pour créer la donnée", "datafeeder.form.dropdown": "Et pour quelle échelle ?", - "datafeeder.form.tags": "Choisissez un ou plusieurs mot-clés correspondant à vos données", + "datafeeder.form.tags": "Choisissez un ou plusieurs mots-clés correspondant à vos données", "datafeeder.form.title": "Donnez un titre à votre jeu de données", - "datafeeder.formsPage.title": "Dites nous en plus sur ces données", + "datafeeder.formsPage.title": "Dites-nous en plus sur ces données", "datafeeder.month.april": "Avril", "datafeeder.month.august": "Août", "datafeeder.month.december": "Décembre", @@ -74,19 +74,19 @@ "datafeeder.month.november": "Novembre", "datafeeder.month.october": "Octobre", "datafeeder.month.september": "Septembre", - "datafeeder.publish.hint": "Vous pouvez quitter cette page en sécurité, vous serez prévenus quand le processus sera terminé", + "datafeeder.publish.hint": "Vous pouvez quitter cette page en toute sécurité, vous serez prévenus quand le processus sera terminé", "datafeeder.publish.illustration.title": "Une autre donnée \n à publier ?", "datafeeder.publish.subtitle": "La publication peut prendre plusieurs minutes.", "datafeeder.publish.title": "Merci! \n Vos données sont en cours de publication", - "datafeeder.publish.upload": "Upload maintenant", - "datafeeder.publishSuccess.geonetworkRecord": "Fiche de métadonnée", + "datafeeder.publish.upload": "Télécharger maintenant", + "datafeeder.publishSuccess.geonetworkRecord": "Fiche de métadonnées", "datafeeder.publishSuccess.illustration.title": "Terminé, tout s'est bien passé !", "datafeeder.publishSuccess.mapViewer": "Visualiseur", "datafeeder.publishSuccess.ogcFeature": "OGC API", "datafeeder.publishSuccess.subtitle": "Visualisez vos données :", "datafeeder.publishSuccess.title": "Félicitations! \n Vos données ont été publiées", "datafeeder.publishSuccess.uploadAnotherData": "Importer une autre donnée", - "datafeeder.summarizePage.illustration": "pas d'erreur ? c'est parti !", + "datafeeder.summarizePage.illustration": "Pas d'erreur ? C'est parti !", "datafeeder.summarizePage.previous": "Précédent", "datafeeder.summarizePage.submit": "Publier", "datafeeder.summarizePage.title": "On y est presque...", @@ -120,10 +120,10 @@ "datafeeder.validation.csv.quote.none": "Aucun", "datafeeder.validation.csv.quote.simple": "Simple guillemet", "datafeeder.validation.csv.quoteChar": "Séparateur de texte", - "datafeeder.validation.encoding": "encodage", + "datafeeder.validation.encoding": "Encodage", "datafeeder.validation.extent.title": "Voici l'emprise du jeu de données", "datafeeder.validation.extent.title.unknown": "Le système de projection est inconnu", - "datafeeder.validation.projection": "Projection:", + "datafeeder.validation.projection": "Projection :", "datafeeder.validation.projection.unknown": "Choisissez une projection", "datafeeder.validation.sample.title": "Et un exemple d'objet", "datafeeder.wizard.emptyRequiredValuesMessage": "Veuillez remplir les champs obligatoires", @@ -190,11 +190,11 @@ "downloads.format.unknown": "inconnu", "downloads.wfs.featuretype.not.found": "La couche n'a pas été retrouvée", "dropFile": "Faites glisser votre fichier", - "editor.form.keywords.placeholder": "Sélectionner un mot-clé", + "editor.form.keywords.placeholder": "Sélectionnez un mot-clé", "editor.form.placeKeywordWithoutExtent": "Ce mot-clé n'a pas de localisation géographique associée", "editor.record.delete.confirmation.cancelText": "Annuler", "editor.record.delete.confirmation.confirmText": "Supprimer", - "editor.record.delete.confirmation.message": "Etes-vous sûr de vouloir supprimer cette fiche ?", + "editor.record.delete.confirmation.message": "Êtes-vous sûr de vouloir supprimer cette fiche ?", "editor.record.delete.confirmation.title": "Supprimer la fiche ?", "editor.record.deleteError.body": "La fiche n'a pas pu être supprimée :", "editor.record.deleteError.closeMessage": "Compris", @@ -234,36 +234,36 @@ "editor.record.form.license.odbl": "", "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", - "editor.record.form.license.unknown": "Non-reconnue ou absente", - "editor.record.form.page.accessAndContact": "Acces et contact", + "editor.record.form.license.unknown": "Non reconnue ou absente", + "editor.record.form.page.accessAndContact": "Accès et contact", "editor.record.form.page.description": "Description de la ressource", "editor.record.form.page.ressources": "Ressources", "editor.record.form.section.about.description": "Ces informations concernent la donnée.", - "editor.record.form.section.about.label": "A propos de la ressource", + "editor.record.form.section.about.label": "À propos de la ressource", "editor.record.form.section.annexes.label": "Annexes", - "editor.record.form.section.associatedResources.description": "Déposez les jeux de données associées à cette fiche de métadonnée.", - "editor.record.form.section.associatedResources.label": "Ressources associees", - "editor.record.form.section.classification.description": "La classification a un impact sur la recherche du jeux de données.", + "editor.record.form.section.associatedResources.description": "Déposez les jeux de données associées à cette fiche de métadonnées.", + "editor.record.form.section.associatedResources.label": "Ressources associées", + "editor.record.form.section.classification.description": "La classification a un impact sur la recherche du jeu de données.", "editor.record.form.section.classification.label": "Classification", "editor.record.form.section.dataManagers.description": "Cette information concerne la donnée.", - "editor.record.form.section.dataManagers.label": "Responsables de la donnee", - "editor.record.form.section.dataPointOfContact.description": "Cette information concerne la fiche de métadonnée.", - "editor.record.form.section.dataPointOfContact.label": "Point de contact de la metadonee", - "editor.record.form.section.geographicalCoverage.label": "Couverture geographique", - "editor.record.form.section.useAndAccessConditions.label": "Conditions d'acces et usage", + "editor.record.form.section.dataManagers.label": "Responsables de la donnée", + "editor.record.form.section.dataPointOfContact.description": "Cette information concerne la fiche de métadonnées.", + "editor.record.form.section.dataPointOfContact.label": "Point de contact de la métadonnée", + "editor.record.form.section.geographicalCoverage.label": "Couverture géographique", + "editor.record.form.section.useAndAccessConditions.label": "Conditions d'accès et usage", "editor.record.form.temporalExtents.addDate": "Date déterminée", "editor.record.form.temporalExtents.addRange": "Période de temps", "editor.record.form.temporalExtents.date": "Date concernée", "editor.record.form.temporalExtents.range": "Période concernée", - "editor.record.form.updateFrequency.planned": "Ces données doivent être mise à jour régulièrement.", - "editor.record.importFromExternalFile.failure.body": "Une erreur est survenue pendant l'import de la fiche: ", + "editor.record.form.updateFrequency.planned": "Ces données doivent être mises à jour régulièrement.", + "editor.record.importFromExternalFile.failure.body": "Une erreur est survenue pendant l'import de la fiche : ", "editor.record.importFromExternalFile.failure.title": "Erreur", - "editor.record.importFromExternalFile.success.body": "L'import de la fiche de metadonnée à été realisée avec succès.", - "editor.record.importFromExternalFile.success.title": "Import reussi", + "editor.record.importFromExternalFile.success.body": "L'import de la fiche de métadonnées a été réalisé avec succès.", + "editor.record.importFromExternalFile.success.title": "Import réussi", "editor.record.loadError.body": "La fiche n'a pas pu être chargée :", "editor.record.loadError.closeMessage": "Compris", "editor.record.loadError.title": "Erreur lors du chargement", - "editor.record.onlineResourceError.body": "Une erreur est survenue lors de l'ajout de la resource:", + "editor.record.onlineResourceError.body": "Une erreur est survenue lors de l'ajout de la ressource :", "editor.record.onlineResourceError.closeMessage": "Compris", "editor.record.onlineResourceError.title": "Erreur lors de l'ajout d'une ressource", "editor.record.placeKeywordWithoutLabel": "Localisation sans nom", @@ -281,10 +281,10 @@ "editor.record.saveStatus.recordUpToDate": "La fiche publiée est à jour", "editor.record.undo.confirmation.cancelText": "Garder les modifications", "editor.record.undo.confirmation.confirmText": "Retirer les modifications", - "editor.record.undo.confirmation.message": "Etes-vous sûr de vouloir annuler les modifications apportées à cette fiche ?", + "editor.record.undo.confirmation.message": "Êtes-vous sûr de vouloir annuler les modifications apportées à cette fiche ?", "editor.record.undo.confirmation.title": "Annuler les modifications ?", "editor.record.undo.tooltip.disabled": "Il n'y a pas de modifications en cours sur cette fiche", - "editor.record.undo.tooltip.enabled": "Cliquer sur ce bouton pour annuler les modifications apportées à cette fiche", + "editor.record.undo.tooltip.enabled": "Cliquez sur ce bouton pour annuler les modifications apportées à cette fiche", "editor.record.upToDate": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "Pas encore implémenté", @@ -296,8 +296,8 @@ "facets.block.title.cl_spatialRepresentationType.key": "Représentation spatiale", "facets.block.title.cl_status.key": "Statut", "facets.block.title.creationYearForResource": "Année de création", - "facets.block.title.resolutionScaleDenominator": "Echelle", - "facets.block.title.tag": "Mots clés", + "facets.block.title.resolutionScaleDenominator": "Échelle", + "facets.block.title.tag": "Mots-clés", "facets.block.title.tag.default": "Tag", "facets.block.title.th_regions_tree.default": "Régions", "favorite.not.authenticated.tooltip": "
Connectez-vous pour avoir accès à cette fonctionnalité
", @@ -352,11 +352,11 @@ "map.wms.urlInput.hint": "Entrez l'URL du service WMS", "multiselect.filter.placeholder": "Rechercher", "nav.back": "Retour", - "next": "suivant", + "next": "Suivant", "ogc.unreachable.unknown": "Le service n'est pas accessible", "organisation.filter.placeholder": "Filtrer les résultats", "organisation.sort.sortBy": "Trier par :", - "organisations.hits.found": "{hits, plural, =0{Aucune organisation trouvé} one{1 organisation sur {total} affichée} other{{hits} organisations sur {total} affichées}}", + "organisations.hits.found": "{hits, plural, =0{Aucune organisation trouvée} one{1 organisation sur {total} affichée} other{{hits} organisations sur {total} affichées}}", "organisations.sortBy.nameAsc": "Nom A → Z", "organisations.sortBy.nameDesc": "Nom Z → A", "organisations.sortBy.recordCountAsc": "Données 0 → 9", @@ -366,9 +366,9 @@ "organization.details.mailContact": "Contacter par mail", "organization.header.recordCount": "{count, plural, =0{donnée} one{donnée} other{données}}", "pagination.nextPage": "Page suivante", - "pagination.page": "page", + "pagination.page": "Page", "pagination.pageOf": "sur", - "previous": "précédent", + "previous": "Précédent", "record.action.delete": "Supprimer", "record.action.download": "Télécharger", "record.action.duplicate": "Dupliquer", @@ -391,10 +391,10 @@ "record.metadata.catalog": "Catalogue", "record.metadata.contact": "Contact", "record.metadata.creation": "Date de création", - "record.metadata.details": "A propos de la donnée", + "record.metadata.details": "À propos de la donnée", "record.metadata.download": "Téléchargements", "record.metadata.formats": "Formats", - "record.metadata.keywords": "Mots clés", + "record.metadata.keywords": "Mots-clés", "record.metadata.languages": "Langues", "record.metadata.lastUpdate": "Mis à jour le {date}", "record.metadata.links": "Liens", @@ -406,30 +406,30 @@ "record.metadata.publication": "Date de publication", "record.metadata.publications": "{count, plural, =0{donnée} one{donnée} other{données}}", "record.metadata.quality": "Qualité des métadonnées", - "record.metadata.quality.contact.failed": "Contact n'est pas renseigné", - "record.metadata.quality.contact.success": "Contact est renseigné", - "record.metadata.quality.description.failed": "Description n'est pas renseignée", - "record.metadata.quality.description.success": "Description est renseignée", + "record.metadata.quality.contact.failed": "Le contact n'est pas renseigné", + "record.metadata.quality.contact.success": "Le contact est renseigné", + "record.metadata.quality.description.failed": "La description n'est pas renseignée", + "record.metadata.quality.description.success": "La description est renseignée", "record.metadata.quality.details": "Détails", - "record.metadata.quality.keywords.failed": "Mots clés ne sont pas renseignés", - "record.metadata.quality.keywords.success": "Mots clés sont renseignés", - "record.metadata.quality.legalConstraints.failed": "Contraintes légales ne sont pas renseignées", - "record.metadata.quality.legalConstraints.success": "Contraintes légales sont renseignées", - "record.metadata.quality.organisation.failed": "Producteur n'est pas renseigné", - "record.metadata.quality.organisation.success": "Producteur est renseigné", - "record.metadata.quality.title.failed": "Titre n'est pas renseigné", - "record.metadata.quality.title.success": "Titre est renseigné", - "record.metadata.quality.topic.failed": "Thème n'est pas renseigné", - "record.metadata.quality.topic.success": "Thème est renseigné", - "record.metadata.quality.updateFrequency.failed": "Fréquence de mise à jour n'est pas renseignée", - "record.metadata.quality.updateFrequency.success": "Fréquence de mise à jour est renseignée", + "record.metadata.quality.keywords.failed": "Les mots-clés ne sont pas renseignés", + "record.metadata.quality.keywords.success": "Les mots-clés sont renseignés", + "record.metadata.quality.legalConstraints.failed": "Les contraintes légales ne sont pas renseignées", + "record.metadata.quality.legalConstraints.success": "Les contraintes légales sont renseignées", + "record.metadata.quality.organisation.failed": "Le producteur n'est pas renseigné", + "record.metadata.quality.organisation.success": "Le producteur est renseigné", + "record.metadata.quality.title.failed": "Le titre n'est pas renseigné", + "record.metadata.quality.title.success": "Le titre est renseigné", + "record.metadata.quality.topic.failed": "Le thème n'est pas renseigné", + "record.metadata.quality.topic.success": "Le thème est renseigné", + "record.metadata.quality.updateFrequency.failed": "La fréquence de mise à jour n'est pas renseignée", + "record.metadata.quality.updateFrequency.success": "La fréquence de mise à jour est renseignée", "record.metadata.related": "Voir aussi", "record.metadata.sheet": "Fiche de métadonnées d'origine", "record.metadata.status": "Statut", "record.metadata.status.notPublished": "Non publié", "record.metadata.status.published": "Publié", "record.metadata.technical": "Informations techniques", - "record.metadata.temporalExtent": "Etendue temporelle", + "record.metadata.temporalExtent": "Étendue temporelle", "record.metadata.temporalExtent.fromDateToDate": "Du { start } au { end }", "record.metadata.temporalExtent.sinceDate": "Depuis le { start }", "record.metadata.temporalExtent.untilDate": "Jusqu'au { end }", @@ -444,7 +444,7 @@ "record.metadata.userFeedbacks.anonymousUser": "Pour rédiger un commentaire, veuillez vous identifier.", "record.metadata.userFeedbacks.newAnswer.buttonTitle": "Publier", "record.metadata.userFeedbacks.newAnswer.placeholder": "Répondre...", - "record.metadata.userFeedbacks.newComment.placeholder": "Rédiger votre commentaire ici...", + "record.metadata.userFeedbacks.newComment.placeholder": "Rédigez votre commentaire ici...", "record.metadata.userFeedbacks.sortSelector.choices.newestFirst": "Les plus récents en premier", "record.metadata.userFeedbacks.sortSelector.choices.oldestFirst": "Les plus anciens en premier", "record.metadata.userFeedbacks.sortSelector.label": "Trier par ...", @@ -453,12 +453,12 @@ "record.tab.data": "Tableau", "record.tab.map": "Carte", "record.was.created.time": "a créé ce jeu de données {time}", - "records": "enregistrements", + "records": "Enregistrements", "results.layout.selectOne": "Affichage des résultats", "results.records.hits.displayedOn": "{displayed, plural, =0{Aucun enregistrement} one{1 enregistrement affiché} other{{displayed} enregistrements affichés}} {hits, plural, other{sur {hits} au total.}}", - "results.records.hits.empty.help.html": "Suggestions :
  • Essayez d'autres mots clés
  • Cherchez moins de mots
", + "results.records.hits.empty.help.html": "Suggestions :
  • Essayez d'autres mots-clés
  • Cherchez moins de mots
", "results.records.hits.found": "{hits, plural, =0{Aucune correspondance.} one{1 enregistrement trouvé.} other{{hits} résultats.}}", - "results.records.hits.selected": "{amount, plural, one{1 selectionnée} other{{ amount } sélectionnées}}", + "results.records.hits.selected": "{amount, plural, one{1 sélectionnée} other{{ amount } sélectionnées}}", "results.showMore": "Plus de résultats...", "results.sortBy.dateStamp": "Plus récent", "results.sortBy.popularity": "Popularité", @@ -469,7 +469,7 @@ "search.error.organizationHasNoDataset": "Cette organisation n'a pas encore de données.", "search.error.organizationNotFound": "L'organisation n'a pas pu être trouvée.", "search.error.receivedError": "Erreur retournée", - "search.error.recordHasnolink": "Ce dataset n'a pas encore de lien, réessayez plus tard s'il vous plaît.", + "search.error.recordHasnolink": "Ce jeu de données n'a pas encore de lien, réessayez plus tard s'il vous plaît.", "search.error.recordNotFound": "Cette donnée n'a pu être trouvée.", "search.field.any.placeholder": "Rechercher une donnée...", "search.field.sortBy": "Trier par :", @@ -478,8 +478,8 @@ "search.filters.format": "Formats", "search.filters.inspireKeyword": "Mot-clé INSPIRE", "search.filters.isSpatial": "Données spatiales", - "search.filters.isSpatial.no": "non-géolocalisées", - "search.filters.isSpatial.yes": "géolocalisées", + "search.filters.isSpatial.no": "Non-géolocalisées", + "search.filters.isSpatial.yes": "Géolocalisées", "search.filters.keyword": "Mot-clé", "search.filters.license": "Licence", "search.filters.license.cc-by": "cc-by", @@ -490,7 +490,7 @@ "search.filters.license.odbl": "odbl", "search.filters.license.odc-by": "odc-by", "search.filters.license.pddl": "pddl", - "search.filters.license.unknown": "Non-reconnue ou absente", + "search.filters.license.unknown": "Non reconnue ou absente", "search.filters.maximize": "Agrandir", "search.filters.minimize": "Réduire", "search.filters.myRecords": "Voir mes données", @@ -518,7 +518,7 @@ "tooltip.url.open": "Ouvrir l'URL", "ui.readLess": "Réduire", "ui.readMore": "Lire la suite", - "wfs.featuretype.notfound": "La classe d'objet n'a pas été trouvée dans le service", + "wfs.featuretype.notfound": "La classe d'objets n'a pas été trouvée dans le service", "wfs.geojsongml.notsupported": "Le service ne supporte pas le format GeoJSON ou GML", "wfs.unreachable.cors": "Le service n'est pas accessible en raison de limitations CORS", "wfs.unreachable.http": "Le service a retourné une erreur HTTP", From 90201e28fcb7bf38dc4885ab49070000fe5196a0 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 25 Sep 2024 17:36:22 +0200 Subject: [PATCH 7/7] fix: correct typos and grammatical errors in the English translation file --- translations/en.json | 68 ++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/translations/en.json b/translations/en.json index 57643c9fd1..3ec0245834 100644 --- a/translations/en.json +++ b/translations/en.json @@ -48,17 +48,17 @@ "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Gathering dataset \n information", "datafeeder.analysisProgressBar.illustration.samplingData": "Sampling \n data", "datafeeder.analysisProgressBar.subtitle": "The analysis may take several minutes, please wait.", - "datafeeder.analysisProgressBar.title": "Analyze in progress", + "datafeeder.analysisProgressBar.title": "Analysis in progress", "datafeeder.datasetValidation.datasetInformation": "The provided dataset contains {number} entities", - "datafeeder.datasetValidation.submitButton": "OK, my data are correct", - "datafeeder.datasetValidation.title": "Make sure your data are correct", + "datafeeder.datasetValidation.submitButton": "OK, my data is correct", + "datafeeder.datasetValidation.title": "Make sure your data is correct", "datafeeder.datasetValidation.unknown": " - ", "datafeeder.datasetValidationCsv.explicitLineNumbers": "*The table must display the first 5 lines (excluding the header)
If this is not the case, check that the file is correctly formatted", "datafeeder.datasetValidationCsv.lineNumbers": "Sample of the first 5 lines* of the dataset:", "datafeeder.form.abstract": "How would you describe your dataset?", "datafeeder.form.datepicker": "Do you know when the dataset was created?", - "datafeeder.form.description": "Finally, please describe the process that was used to create the dataset", - "datafeeder.form.dropdown": "For which scale was it created ?", + "datafeeder.form.description": "Finally, please describe the process used to create the dataset", + "datafeeder.form.dropdown": "For which scale was it created?", "datafeeder.form.tags": "Select one or more tags that fit your dataset", "datafeeder.form.title": "Give your dataset the best title", "datafeeder.formsPage.title": "Tell us more about your dataset", @@ -75,7 +75,7 @@ "datafeeder.month.october": "October", "datafeeder.month.september": "September", "datafeeder.publish.hint": "You can safely exit this page, you will be notified when the process is over", - "datafeeder.publish.illustration.title": "Another dataset \n to publish ?", + "datafeeder.publish.illustration.title": "Another dataset \n to publish?", "datafeeder.publish.subtitle": "Publishing may take several minutes.", "datafeeder.publish.title": "Thank you! \n Your dataset is being published", "datafeeder.publish.upload": "Upload it now", @@ -84,9 +84,9 @@ "datafeeder.publishSuccess.mapViewer": "Map viewer", "datafeeder.publishSuccess.ogcFeature": "OGC API", "datafeeder.publishSuccess.subtitle": "View your data in:", - "datafeeder.publishSuccess.title": "Congratulation! \n Your dataset has been published", + "datafeeder.publishSuccess.title": "Congratulations! \n Your dataset has been published", "datafeeder.publishSuccess.uploadAnotherData": "Upload another dataset", - "datafeeder.summarizePage.illustration": "no mistake? let's go!", + "datafeeder.summarizePage.illustration": "No mistake? Let's go!", "datafeeder.summarizePage.previous": "Previous", "datafeeder.summarizePage.submit": "Submit", "datafeeder.summarizePage.title": "You're almost there...", @@ -100,7 +100,7 @@ "datafeeder.upload.error.title.cantOpenFile": "Error while opening the file", "datafeeder.upload.error.title.fileFormat": "The selected file format is not supported", "datafeeder.upload.error.title.fileHasntSelected": "No file selected", - "datafeeder.upload.error.title.fileSize": "The file size is too big", + "datafeeder.upload.error.title.fileSize": "The file size is too large", "datafeeder.upload.error.title.noRightsToSendData": "You are not allowed to publish this dataset", "datafeeder.upload.illustration.enrichment": "Enrichment", "datafeeder.upload.illustration.import": "Import", @@ -118,15 +118,15 @@ "datafeeder.validation.csv.lng.field": "Longitude column", "datafeeder.validation.csv.quote.double": "Double quote", "datafeeder.validation.csv.quote.none": "None", - "datafeeder.validation.csv.quote.simple": "Simple quote", + "datafeeder.validation.csv.quote.simple": "Single quote", "datafeeder.validation.csv.quoteChar": "Quote separator", - "datafeeder.validation.encoding": "encoding", + "datafeeder.validation.encoding": "Encoding", "datafeeder.validation.extent.title": "Here is the dataset extent", "datafeeder.validation.extent.title.unknown": "The projection system is unknown", - "datafeeder.validation.projection": "spatial reference system:", + "datafeeder.validation.projection": "Spatial reference system:", "datafeeder.validation.projection.unknown": "Choose a spatial reference system", "datafeeder.validation.sample.title": "And a sample entity from the dataset", - "datafeeder.wizard.emptyRequiredValuesMessage": "Please fill mandatory fields", + "datafeeder.wizard.emptyRequiredValuesMessage": "Please fill in mandatory fields", "datafeeder.wizardSummarize.createdAt": "Created at", "datafeeder.wizardSummarize.scale": "Scale", "datahub.header.datasets": "Datasets", @@ -144,7 +144,7 @@ "datahub.record.addToFavorites": "Add to favorites", "datahub.search.back": "Back", "datahub.search.filter.all": "All", - "datahub.search.filter.generatedByWfs": "generated by an API", + "datahub.search.filter.generatedByWfs": "Generated by an API", "datahub.search.filter.others": "Others", "dataset.error.http": "The data could not be loaded because of an HTTP error: \"{ info }\"", "dataset.error.network": "The data could not be loaded because of a network error or CORS limitations: \"{ info }\"", @@ -174,7 +174,7 @@ "domain.contact.role.user": "User", "domain.record.status.completed": "Completed", "domain.record.status.deprecated": "Deprecated", - "domain.record.status.ongoing": "On going", + "domain.record.status.ongoing": "Ongoing", "domain.record.status.removed": "Removed", "domain.record.status.under_development": "Under development", "domain.record.updateFrequency.asNeeded": "As needed", @@ -187,9 +187,9 @@ "domain.record.updateFrequency.unknown": "Unknown", "domain.record.updateFrequency.week": "{count, plural, =0{0 times} one{Once} other{{count} times}} per week", "domain.record.updateFrequency.year": "{count, plural, =0{0 times} one{Once} other{{count} times}} per year", - "downloads.format.unknown": "unknown", + "downloads.format.unknown": "Unknown", "downloads.wfs.featuretype.not.found": "The layer was not found", - "dropFile": "drop file", + "dropFile": "Drop file", "editor.form.keywords.placeholder": "Select a keyword", "editor.form.placeKeywordWithoutExtent": "This keyword is not associated with a geographical extent", "editor.record.delete.confirmation.cancelText": "Cancel", @@ -199,7 +199,7 @@ "editor.record.deleteError.body": "The record could not be deleted:", "editor.record.deleteError.closeMessage": "Understood", "editor.record.deleteError.title": "Error deleting record", - "editor.record.deleteSuccess.body": "The record was successfully deleted !", + "editor.record.deleteSuccess.body": "The record was successfully deleted!", "editor.record.deleteSuccess.title": "Delete success", "editor.record.form.bottomButtons.comeBackLater": "Come back later", "editor.record.form.bottomButtons.next": "Next", @@ -258,12 +258,12 @@ "editor.record.form.updateFrequency.planned": "The data should be updated regularly.", "editor.record.importFromExternalFile.failure.body": "Failure", "editor.record.importFromExternalFile.failure.title": "The import of the record has failed: ", - "editor.record.importFromExternalFile.success.body": "Import succesful", - "editor.record.importFromExternalFile.success.title": "The record has been succefuly imported.", + "editor.record.importFromExternalFile.success.body": "Import successful", + "editor.record.importFromExternalFile.success.title": "The record has been successfully imported.", "editor.record.loadError.body": "The record could not be loaded:", "editor.record.loadError.closeMessage": "Understood", "editor.record.loadError.title": "Error loading record", - "editor.record.onlineResourceError.body": "An error happened while adding the resource:", + "editor.record.onlineResourceError.body": "An error occurred while adding the resource:", "editor.record.onlineResourceError.closeMessage": "Understood", "editor.record.onlineResourceError.title": "Error adding resource", "editor.record.placeKeywordWithoutLabel": "Unnamed location", @@ -283,7 +283,7 @@ "editor.record.undo.confirmation.confirmText": "Discard the changes", "editor.record.undo.confirmation.message": "Are you sure you want to cancel the pending changes on this record?", "editor.record.undo.confirmation.title": "Cancel changes?", - "editor.record.undo.tooltip.disabled": "There is no pending changes on this record", + "editor.record.undo.tooltip.disabled": "There are no pending changes on this record", "editor.record.undo.tooltip.enabled": "Clicking this button will cancel the pending changes on this record.", "editor.record.upToDate": "This record is up to date", "editor.sidebar.menu.editor": "Editor", @@ -352,7 +352,7 @@ "map.wms.urlInput.hint": "Enter WMS service URL", "multiselect.filter.placeholder": "Search", "nav.back": "Back", - "next": "next", + "next": "Next", "ogc.unreachable.unknown": "The service could not be reached", "organisation.filter.placeholder": "Filter results", "organisation.sort.sortBy": "Sort by:", @@ -366,9 +366,9 @@ "organization.details.mailContact": "Contact by email", "organization.header.recordCount": "{count, plural, =0{data} one{data} other{datas}}", "pagination.nextPage": "Next page", - "pagination.page": "page", + "pagination.page": "Page", "pagination.pageOf": "of", - "previous": "previous", + "previous": "Previous", "record.action.delete": "Delete", "record.action.download": "Download", "record.action.duplicate": "Duplicate", @@ -380,9 +380,9 @@ "record.metadata.api.form.closeForm": "Close the form", "record.metadata.api.form.create": "Create your request", "record.metadata.api.form.customUrl": "Custom URL", - "record.metadata.api.form.limit": "Count of records", + "record.metadata.api.form.limit": "Number of records", "record.metadata.api.form.limit.all": "All", - "record.metadata.api.form.offset": "Count of first record", + "record.metadata.api.form.offset": "Number of first record", "record.metadata.api.form.openForm": "Open the form", "record.metadata.api.form.reset": "Reset", "record.metadata.api.form.title": "Generate a custom URL", @@ -467,7 +467,7 @@ "search.autocomplete.error": "Suggestions could not be fetched:", "search.error.couldNotReachApi": "The API could not be reached", "search.error.organizationHasNoDataset": "This organization has no dataset yet.", - "search.error.organizationNotFound": "This organization could not be found.", + "search.error.organizationNotFound": "This organization could not be found.", "search.error.receivedError": "An error was received", "search.error.recordHasnolink": "This record currently has no link yet, please come back later.", "search.error.recordNotFound": "The record with identifier \"{ id }\" could not be found.", @@ -478,8 +478,8 @@ "search.filters.format": "Formats", "search.filters.inspireKeyword": "INSPIRE keyword", "search.filters.isSpatial": "Is spatial data", - "search.filters.isSpatial.no": "non spatial", - "search.filters.isSpatial.yes": "spatial", + "search.filters.isSpatial.no": "Non-spatial", + "search.filters.isSpatial.yes": "Spatial", "search.filters.keyword": "Keyword", "search.filters.license": "License", "search.filters.license.cc-by": "Creative Commons CC-BY", @@ -494,7 +494,7 @@ "search.filters.maximize": "Expand", "search.filters.minimize": "Minimize", "search.filters.myRecords": "Show only my records", - "search.filters.myRecordsHelp": "When this is enabled, records only created by myself are shown; records created by others will not show up.", + "search.filters.myRecordsHelp": "When this is enabled, only records created by me are shown; records created by others will not appear.", "search.filters.organization": "Organization", "search.filters.otherRecords": "Showing records from another person", "search.filters.producerOrg": "Producer", @@ -506,11 +506,11 @@ "search.filters.title": "Filter your results", "search.filters.topic": "Topics", "search.filters.useSpatialFilter": "Show records in the area of interest first", - "search.filters.useSpatialFilterHelp": "When this is enabled, records situated in the catalog's area of interest are shown first; records outside of this area will not show up.", + "search.filters.useSpatialFilterHelp": "When this is enabled, records within the catalog's area of interest are shown first; records outside of this area will not appear.", "share.tab.permalink": "Share", "share.tab.webComponent": "Integrate", "table.loading.data": "Loading data...", - "table.object.count": "objects in this dataset", + "table.object.count": "Objects in this dataset", "table.select.data": "Data source", "tooltip.html.copy": "Copy HTML", "tooltip.id.copy": "Copy unique identifier", @@ -520,7 +520,7 @@ "ui.readMore": "Read more", "wfs.featuretype.notfound": "No matching feature type was found in the service", "wfs.geojsongml.notsupported": "This service does not support the GeoJSON or GML format", - "wfs.unreachable.cors": "The service could not be reached because of CORS limitations", + "wfs.unreachable.cors": "The service could not be reached due to CORS limitations", "wfs.unreachable.http": "The service returned an HTTP error", "wfs.unreachable.unknown": "The service could not be reached" }