From 213143c0f71f79163f9c6b0218d47f922d294eac Mon Sep 17 00:00:00 2001 From: Hassane Diaby Date: Thu, 13 Jun 2024 18:03:25 +0200 Subject: [PATCH] story #12867 : New descriptive field of vocabulary on Ontology --- .../ontology/OntologyInternalService.java | 49 ++++++++++--------- .../ontology-create.component.html | 33 +++++++++++-- .../ontology-create.component.spec.ts | 2 + .../ontology-create.component.ts | 19 ++++++- .../ontology-information-tab.component.html | 32 ++++++++++-- ...ontology-information-tab.component.spec.ts | 2 + .../ontology-information-tab.component.ts | 43 +++++++++++----- .../referential/src/assets/i18n/en.json | 12 ++++- .../referential/src/assets/i18n/fr.json | 12 ++++- .../modules/models/ontology/ontology.utils.ts | 48 ++++++++++++++++++ .../src/lib/models/ontology.ts | 2 + 11 files changed, 206 insertions(+), 48 deletions(-) create mode 100644 ui/ui-frontend/projects/vitamui-library/src/app/modules/models/ontology/ontology.utils.ts diff --git a/api/api-referential/referential-internal/src/main/java/fr/gouv/vitamui/referential/internal/server/ontology/OntologyInternalService.java b/api/api-referential/referential-internal/src/main/java/fr/gouv/vitamui/referential/internal/server/ontology/OntologyInternalService.java index 07e320906e0..31e9683c476 100644 --- a/api/api-referential/referential-internal/src/main/java/fr/gouv/vitamui/referential/internal/server/ontology/OntologyInternalService.java +++ b/api/api-referential/referential-internal/src/main/java/fr/gouv/vitamui/referential/internal/server/ontology/OntologyInternalService.java @@ -50,6 +50,8 @@ import fr.gouv.vitam.common.model.administration.OntologyModel; import fr.gouv.vitam.common.model.administration.OntologyOrigin; import fr.gouv.vitam.common.model.administration.OntologyType; +import fr.gouv.vitam.common.model.administration.StringSize; +import fr.gouv.vitam.common.model.administration.TypeDetail; import fr.gouv.vitamui.commons.api.domain.DirectionDto; import fr.gouv.vitamui.commons.api.domain.PaginatedValuesDto; import fr.gouv.vitamui.commons.api.dtos.VitamUiOntologyDto; @@ -245,6 +247,12 @@ public OntologyDto patch(VitamContext vitamContext, final Map pa partialDto.forEach((key, value) -> { if ("type".equals(key)) { ontologyDto.setType(OntologyType.valueOf((String) value)); + } else if ("stringSize".equals(key)) { + ontologyDto.setStringSize( + Optional.ofNullable(value).map(v -> StringSize.valueOf((String) v)).orElse(null) + ); + } else if ("typeDetail".equals(key)) { + ontologyDto.setTypeDetail(TypeDetail.valueOf((String) value)); } else if (!"id".equals(key)) { try { BeanUtilsBean.getInstance().copyProperty(ontologyDto, key, value); @@ -257,45 +265,38 @@ public OntologyDto patch(VitamContext vitamContext, final Map pa try { updateOntology(vitamContext, (String) partialDto.get("id"), ontologyDto); return ontologyDto; - } catch (InvalidParseOperationException | AccessExternalClientException | IOException e) { + } catch ( + InvalidParseOperationException | AccessExternalClientException | IOException | InternalServerException e + ) { throw new InternalServerException("Unable to patch agency", e); } } - private RequestResponse updateOntology( - final VitamContext vitamContext, - final String id, - OntologyDto patchOntology - ) throws InvalidParseOperationException, AccessExternalClientException, IOException { + private void updateOntology(final VitamContext vitamContext, final String id, OntologyDto patchOntology) + throws InvalidParseOperationException, AccessExternalClientException, IOException, InternalServerException { if (vitamContext != null) { LOGGER.info("Update Ontology EvIdAppSession : {} ", vitamContext.getApplicationSessionId()); } final List ontologies = getAll(vitamContext); - ontologies + final OntologyDto matchedOntology = ontologies .stream() .filter(ontology -> id.equals(ontology.getId())) - .forEach(ontology -> this.patchFields(ontology, patchOntology)); + .findFirst() + .orElseThrow(() -> new InternalServerException("No ontology matched for update")); - return ontologyService.importOntologies(vitamContext, converter.convertDtosToVitams(ontologies)); - } + ontologies.remove(matchedOntology); - private void patchFields(OntologyDto ontologyToPatch, OntologyDto fieldsToApply) { - if (fieldsToApply.getShortName() != null) { - ontologyToPatch.setShortName(fieldsToApply.getShortName()); - } + matchedOntology.setShortName(patchOntology.getShortName()); + matchedOntology.setDescription(patchOntology.getDescription()); + matchedOntology.setType(patchOntology.getType()); + matchedOntology.setCollections(patchOntology.getCollections()); + matchedOntology.setStringSize(patchOntology.getStringSize()); + matchedOntology.setTypeDetail(patchOntology.getTypeDetail()); - if (fieldsToApply.getDescription() != null) { - ontologyToPatch.setDescription(fieldsToApply.getDescription()); - } - - if (fieldsToApply.getType() != null) { - ontologyToPatch.setType(fieldsToApply.getType()); - } + ontologies.add(patchOntology); - if (fieldsToApply.getCollections() != null) { - ontologyToPatch.setCollections(fieldsToApply.getCollections()); - } + ontologyService.importOntologies(vitamContext, converter.convertDtosToVitams(ontologies)); } public JsonNode findHistoryByIdentifier(VitamContext vitamContext, final String id) throws VitamClientException { diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.html b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.html index 79ff3f7ec48..ffd652b407d 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.html +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.html @@ -30,12 +30,13 @@ > -
+
@@ -47,11 +48,20 @@ +
- + {{ type.label }} @@ -60,14 +70,29 @@
+
+ + + + {{ size.label }} + + +
keyboard_arrow_down
+
+ +
+
- + {{ collection.label }} diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.spec.ts b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.spec.ts index 54a8c19284f..eb91726020f 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.spec.ts +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.spec.ts @@ -60,6 +60,8 @@ const expectedOntology = { collections: ['ObjectGroup'], description: 'Mon Ontologie', origin: 'INTERNAL', + typeDetail: 'STRING', + stringSize: 'MEDIUM', }; let component: OntologyCreateComponent; diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.ts b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.ts index 721b5c16b38..f8d9ea5ca39 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.ts +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-create/ontology-create.component.ts @@ -41,6 +41,7 @@ import { Subscription } from 'rxjs'; import { ConfirmDialogService, Option } from 'vitamui-library'; import { OntologyService } from '../ontology.service'; import { OntologyCreateValidators } from './ontology-create.validators'; +import { setTypeDetailAndStringSize } from '../../../../../vitamui-library/src/app/modules/models/ontology/ontology.utils'; @Component({ selector: 'app-ontology-create', @@ -54,6 +55,7 @@ export class OntologyCreateComponent implements OnInit, OnDestroy { hasError = true; message: string; isDisabledButton = false; + sizeFieldVisible = false; // stepCount is the total number of steps and is used to calculate the advancement of the progress bar. // We could get the number of steps using ViewChildren(StepComponent) but this triggers a @@ -78,6 +80,12 @@ export class OntologyCreateComponent implements OnInit, OnDestroy { { key: 'ObjectGroup', label: "Groupe d'objet", info: '' }, ]; + sizes: Option[] = [ + { key: 'SHORT', label: 'Court', info: '' }, + { key: 'MEDIUM', label: 'Moyen', info: '' }, + { key: 'LARGE', label: 'Long', info: '' }, + ]; + @ViewChild('fileSearch', { static: false }) fileSearch: any; constructor( @@ -91,10 +99,12 @@ export class OntologyCreateComponent implements OnInit, OnDestroy { ngOnInit() { this.form = this.formBuilder.group({ - shortName: [null], + shortName: [null, Validators.required], identifier: [null, [Validators.required, this.ontologyCreateValidator.patternID()], this.ontologyCreateValidator.uniqueID()], type: [null, Validators.required], - collections: [null], + typeDetail: [null], + stringSize: [null], + collections: [null, Validators.required], description: [null], origin: ['INTERNAL'], }); @@ -114,6 +124,11 @@ export class OntologyCreateComponent implements OnInit, OnDestroy { } } + onIndexingModeChange(key: string) { + this.sizeFieldVisible = key === 'TEXT'; + setTypeDetailAndStringSize(key, this.form); + } + onSubmit() { if (this.form.invalid) { this.isDisabledButton = true; diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.html b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.html index a1454836996..ae5aa66c223 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.html +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.html @@ -1,12 +1,22 @@
- + + +
+ +
+
- + {{ type.label }} @@ -15,6 +25,22 @@
+
+ + + + {{ size.label }} + + +
keyboard_arrow_down
+
+
+
-
diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.spec.ts b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.spec.ts index 9fe5bafd369..2aa7879cc1c 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.spec.ts +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.spec.ts @@ -34,6 +34,8 @@ describe('OntologyInformationTabComponent', () => { type: 'EXTERNAL', collections: [''], description: 'Mon Ontologie', + typeDetail: 'string', + stringSize: 'MEDIUM', }; beforeEach(waitForAsync(() => { diff --git a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.ts b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.ts index 05745568268..6ced7e37a9d 100644 --- a/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.ts +++ b/ui/ui-frontend/projects/referential/src/app/ontology/ontology-preview/ontology-information-tab/ontology-information-tab.component.ts @@ -1,12 +1,11 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Observable, of } from 'rxjs'; -import { catchError, filter, map, switchMap } from 'rxjs/operators'; -import { Option, diff } from 'vitamui-library'; -import { extend, isEmpty } from 'underscore'; -import { Ontology } from 'vitamui-library'; -import { OntologyService } from '../../ontology.service'; +import { catchError, switchMap } from 'rxjs/operators'; +import { diff, Ontology, Option } from 'vitamui-library'; import { RULE_TYPES } from '../../../rule/rules.constants'; +import { OntologyService } from '../../ontology.service'; +import { setTypeDetailAndStringSize } from '../../../../../../vitamui-library/src/app/modules/models/ontology/ontology.utils'; @Component({ selector: 'app-ontology-information-tab', @@ -21,6 +20,8 @@ export class OntologyInformationTabComponent implements OnInit { submited = false; + sizeFieldVisible = false; + // FIXME: Get list from common var ? types: Option[] = [ { key: 'DATE', label: 'Date', info: '' }, @@ -39,6 +40,12 @@ export class OntologyInformationTabComponent implements OnInit { { key: 'ObjectGroup', label: "Groupe d'objet", info: '' }, ]; + sizes: Option[] = [ + { key: 'SHORT', label: 'Court', info: '' }, + { key: 'MEDIUM', label: 'Moyen', info: '' }, + { key: 'LARGE', label: 'Long', info: '' }, + ]; + @Input() set inputOntology(ontology: Ontology) { this._inputOntology = ontology; @@ -53,6 +60,8 @@ export class OntologyInformationTabComponent implements OnInit { this._inputOntology.collections = []; } + this.sizeFieldVisible = this._inputOntology.type === 'TEXT'; + this.resetForm(this.inputOntology); this.updated.emit(false); } @@ -86,7 +95,9 @@ export class OntologyInformationTabComponent implements OnInit { identifier: [null, Validators.required], shortName: [{ value: null, disabled: true }, Validators.required], type: [null, Validators.required], - collections: [null], + typeDetail: [null], + stringSize: [null], + collections: [null, Validators.required], description: [null], creationDate: [{ value: null, disabled: true }], }); @@ -104,12 +115,22 @@ export class OntologyInformationTabComponent implements OnInit { return unchanged; } + onIndexingModeChange(key: string) { + if (!this.isInternal) { + this.sizeFieldVisible = key === 'TEXT'; + } + + setTypeDetailAndStringSize(key, this.form); + } + prepareSubmit(): Observable { - return of(diff(this.form.getRawValue(), this.previousValue())).pipe( - filter((formData) => !isEmpty(formData)), - map((formData) => extend({ id: this.previousValue().id, identifier: this.previousValue().identifier }, formData)), - switchMap((formData: { id: string; [key: string]: any }) => this.ontologyService.patch(formData).pipe(catchError(() => of(null)))), - ); + const payload = { + id: this.previousValue().id, + identifier: this.previousValue().identifier, + ...this.form.value, + }; + + return of(payload).pipe(switchMap((formData: any) => this.ontologyService.patch(formData).pipe(catchError(() => of(null))))); } onSubmit() { diff --git a/ui/ui-frontend/projects/referential/src/assets/i18n/en.json b/ui/ui-frontend/projects/referential/src/assets/i18n/en.json index cc5259d1338..dae2196db8e 100644 --- a/ui/ui-frontend/projects/referential/src/assets/i18n/en.json +++ b/ui/ui-frontend/projects/referential/src/assets/i18n/en.json @@ -619,12 +619,18 @@ "SUBTITLE": "Information", "IDENTIFIER": "Identifier", "NAME": "Name", - "TYPE": "Type", + "INDEXING_MODE": "Indexing mode", + "FIELD_SIZE": "Field size", "COLLECTION": "Collection", "DESCRIPTION": "Description", + "SHORT": "Short", + "MEDIUM": "Medium", + "LONG": "Long", "IDENTIFIER_TOOLTIP": "For external vocabularies, this identifier corresponds to the name of the metadata as it is named in the transfer slip or in an archival unit profile", - "TYPE_TOOLTIP": "Vocabulary indexing type, corresponding to a type expected by the Elastic Search engine", + "INDEXING_MODE_TOOLTIP": "Vocabulary indexing type, corresponding to a type expected by the Elastic Search engine", "COLLECTION_TOOLTIP": "Mongo DB collection that uses the vocabulary", + "NAME_TOOLTIP": "Field label as it will display in search and consultation of this metadata", + "SIZE_TOOLTIP": "The expected value for this metadata corresponds to \"Short\" for few words, to \"Medium\" for a sentence and \"Long\" for a paragraph", "NAME_ALREADY_EXISTS": "Name already in use", "IDENTIFIER_ALREADY_EXISTS": "Identifier already in use", "IDENTIFIER_NO_COMPLIANT": "Non-compliant identifier" @@ -632,8 +638,10 @@ "TAB": { "INFORMATION": { "TITLE": "Information", + "IDENTIFIER": "Identifier", "NAME": "Name", "TYPE": "Type", + "FIELD_SIZE": "Field size", "COLLECTION": "Collection", "DESCRIPTION": "Description", "CREATION_DATE": "Creation date" diff --git a/ui/ui-frontend/projects/referential/src/assets/i18n/fr.json b/ui/ui-frontend/projects/referential/src/assets/i18n/fr.json index a128f2cb30a..f7b1f81585e 100644 --- a/ui/ui-frontend/projects/referential/src/assets/i18n/fr.json +++ b/ui/ui-frontend/projects/referential/src/assets/i18n/fr.json @@ -619,12 +619,18 @@ "SUBTITLE": "Informations", "IDENTIFIER": "Identifiant", "NAME": "Nom", - "TYPE": "Type", + "INDEXING_MODE": "Mode d'indexation", + "FIELD_SIZE": "Taille du champ", "COLLECTION": "Collection", "DESCRIPTION": "Description", + "SHORT": "Court", + "MEDIUM": "Moyen", + "LONG": "Long", "IDENTIFIER_TOOLTIP": "Pour les vocabulaires externes, cet identifiant correspond au nom de la métadonnée telle qu'elle est nommée dans le bordereau de transfert ou dans un profil d'unité archivistique", - "TYPE_TOOLTIP": "Type d'indexation du vocabulaire, correspondant à un type attendu par le moteur Elastic Search", + "INDEXING_MODE_TOOLTIP": "Type d'indexation du vocabulaire, correspondant à un type attendu par le moteur Elastic Search", "COLLECTION_TOOLTIP": "Collection de la base Mongo DB qui utilise le vocabulaire", + "NAME_TOOLTIP": "Label du champ tel qu'il s’affichera en recherche et consultation de cette métadonnée", + "SIZE_TOOLTIP": "La valeur attendue pour cette métadonnée correspond pour \"Court\" à quelques mots ; pour \"Moyen\" à une phrase et pour \"Long\" à un paragraphe", "NAME_ALREADY_EXISTS": "Nom déjà utilisé", "IDENTIFIER_ALREADY_EXISTS": "Identifiant déjà utilisé", "IDENTIFIER_NO_COMPLIANT": "Identifiant non conforme" @@ -632,8 +638,10 @@ "TAB": { "INFORMATION": { "TITLE": "Informations", + "IDENTIFIER": "Identifiant", "NAME": "Nom", "TYPE": "Type", + "FIELD_SIZE": "Taille du champ", "COLLECTION": "Collection", "DESCRIPTION": "Description", "CREATION_DATE": "Date de création" diff --git a/ui/ui-frontend/projects/vitamui-library/src/app/modules/models/ontology/ontology.utils.ts b/ui/ui-frontend/projects/vitamui-library/src/app/modules/models/ontology/ontology.utils.ts new file mode 100644 index 00000000000..f2c21b83648 --- /dev/null +++ b/ui/ui-frontend/projects/vitamui-library/src/app/modules/models/ontology/ontology.utils.ts @@ -0,0 +1,48 @@ +/** + * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2022) + * + * contact.vitam@culture.gouv.fr + * + * This software is a computer program whose purpose is to implement a digital archiving back-office system managing + * high volumetry securely and efficiently. + * + * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free + * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as + * circulated by CEA, CNRS and INRIA at the following URL "https://cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, + * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the + * successive licensors have only limited liability. + * + * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or + * developing or reproducing the software by the user in light of its specific status of free software, that may mean + * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and + * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the + * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data + * to be ensured and, more generally, to use and operate it in the same conditions as regards security. + * + * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you + * accept its terms. + */ +import { FormGroup } from '@angular/forms'; + +export function setTypeDetailAndStringSize(key: string, form: FormGroup) { + switch (key) { + case 'TEXT': + case 'KEYWORD': + case 'GEO_POINT': + form.get('typeDetail').setValue('STRING'); + form.get('stringSize').setValue('MEDIUM'); + break; + case 'DATE': + form.get('typeDetail').setValue('DATETIME'); + form.get('stringSize').setValue(null); + break; + case 'ENUM': + case 'LONG': + case 'DOUBLE': + case 'BOOLEAN': + form.get('typeDetail').setValue(key); + form.get('stringSize').setValue(null); + } +} diff --git a/ui/ui-frontend/projects/vitamui-library/src/lib/models/ontology.ts b/ui/ui-frontend/projects/vitamui-library/src/lib/models/ontology.ts index 798cdfa135a..3375282f52b 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/lib/models/ontology.ts +++ b/ui/ui-frontend/projects/vitamui-library/src/lib/models/ontology.ts @@ -13,5 +13,7 @@ export interface Ontology extends Id { apiField: string; type: string; origin: string; + typeDetail: string; + stringSize: string; collections: Array; }