Skip to content

Commit

Permalink
Component to allow checking duplicated metadata values for title, alt…
Browse files Browse the repository at this point in the history
…ernative title and resource identifier in the metadata editor and display a message to the user (#6984) (#8516)

* Components to allow to check duplicated metadata titles in the metadata editor and display a message to the user:

* Automatic formatting

* Simplify duplicate titles search

* Update the component to check duplicates in the following fields:
- Metadata title
- Metadata alternative title
- Metadata resource identifier

---------

Co-authored-by: Juan Luis Rodríguez <[email protected]>
  • Loading branch information
josegar74 and juanluisrp authored Nov 26, 2024
1 parent fc96dfb commit 7f8663b
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@

<for name="mrs:referenceSystemIdentifier" addDirective="data-gn-crs-selector"/>

<!-- Example configuration to check duplicated metadata alternate title -->
<!--<for name="cit:alternateTitle"
xpath="/mdb:MD_Metadata/mdb:identificationInfo/mri:MD_DataIdentification/mri:citation/cit:CI_Citation/cit:alternateTitle"
use="data-gn-duplicated-metadata-value-checker">
<directiveAttributes
data-field-key="altTitle" />
</for>-->

<for name="mdb:contact" addDirective="data-gn-directory-entry-selector">
<directiveAttributes
data-template-add-snippet="&lt;cit:CI_Responsibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
</for>
-->

<!-- Check if metadata title is defined in other metadata record
<for name="gmd:title" use="data-gn-duplicated-metadata-title-checker" />
-->

<for name="gts:TM_PeriodDuration" use="data-gn-field-duration-div"/>
<for name="gml:duration" use="data-gn-field-duration-div"/>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2023 Food and Agriculture Organization of the
* Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand Down Expand Up @@ -51,6 +51,7 @@
import org.fao.geonet.kernel.SchemaManager;
import org.fao.geonet.kernel.datamanager.IMetadataUtils;
import org.fao.geonet.kernel.mef.MEFLib;
import org.fao.geonet.kernel.search.EsSearchManager;
import org.fao.geonet.lib.Lib;
import org.fao.geonet.repository.MetadataRepository;
import org.fao.geonet.utils.Log;
Expand All @@ -63,6 +64,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -108,6 +110,9 @@ public class MetadataApi {

private ApplicationContext context;

@Autowired
EsSearchManager esSearchManager;

public static RelatedResponse getRelatedResources(
String language, ServiceContext context,
AbstractMetadata md, RelatedItemType[] type, int start, int rows) throws Exception {
Expand Down Expand Up @@ -780,6 +785,47 @@ public FeatureResponse getFeatureCatalog(

}

@io.swagger.v3.oas.annotations.Operation(summary = "Check if metadata field value is duplicated in another metadata",
description = "Verifies if a metadata field value is in use. Fields supported: title (title), " +
"alternate title (altTitle) or resource identifier (identifier)")
@PostMapping(value = "/{metadataUuid:.+}/checkDuplicatedFieldValue",
produces = {MediaType.APPLICATION_JSON_VALUE})
@PreAuthorize("hasAuthority('Editor')")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Return true if the field value is duplicated in another metadata or false in other case."),
@ApiResponse(responseCode = "403", description = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)
})
public ResponseEntity<Boolean> checkDuplicatedFieldValue(
@Parameter(description = API_PARAM_RECORD_UUID,
required = true)
@PathVariable
String metadataUuid,
@Parameter(description = "Metadata field information to check",
required = true)
@RequestBody DuplicatedValueDto duplicatedValueDto,
HttpServletRequest request
) throws Exception {
try {
ApiUtils.canViewRecord(metadataUuid, request);
} catch (SecurityException e) {
Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e);
throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW);
}

List<String> validFields = Arrays.asList("title", "altTitle", "identifier");

if (!validFields.contains(duplicatedValueDto.getField())) {
throw new IllegalArgumentException(String.format("A valid field name is required:", String.join(",", validFields)));
}

if (StringUtils.isEmpty(duplicatedValueDto.getValue())) {
throw new IllegalArgumentException("A non-empty value is required.");
}


boolean uuidsWithSameTitle = MetadataUtils.isMetadataFieldValueExistingInOtherRecords(duplicatedValueDto.getValue(), duplicatedValueDto.getField(), metadataUuid);
return ResponseEntity.ok(uuidsWithSameTitle);
}

private boolean isIncludedAttributeTable(RelatedResponse.Fcat fcat) {
return fcat != null
Expand All @@ -789,4 +835,25 @@ private boolean isIncludedAttributeTable(RelatedResponse.Fcat fcat) {
&& fcat.getItem().get(0).getFeatureType().getAttributeTable() != null
&& fcat.getItem().get(0).getFeatureType().getAttributeTable().getElement() != null;
}

private static class DuplicatedValueDto {
private String field;
private String value;

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2023 Food and Agriculture Organization of the
* Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand Down Expand Up @@ -37,6 +37,7 @@
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.GeonetContext;
import org.fao.geonet.NodeInfo;
import org.fao.geonet.api.API;
import org.fao.geonet.api.es.EsHTTPProxy;
import org.fao.geonet.api.records.model.related.AssociatedRecord;
import org.fao.geonet.api.records.model.related.RelatedItemOrigin;
Expand Down Expand Up @@ -768,6 +769,48 @@ public static boolean retrieveMetadataValidationStatus(AbstractMetadata metadata
return isInvalid;
}

/**
* Check if other metadata records exist apart from the one with {code}metadataUuidToExclude{code} with the same
* {code}metadataValue{code} for the field {code}metadataField{code}.
*
* @param metadataValue Metadata value to check.
* @param metadataField Metadata field to check the value.
* @param metadataUuidToExclude Metadata identifier to exclude from the search.
* @return A list of metadata uuids that have the same value for the field provided.
*/
public static boolean isMetadataFieldValueExistingInOtherRecords(String metadataValue, String metadataField, String metadataUuidToExclude) {
ApplicationContext applicationContext = ApplicationContextHolder.get();
EsSearchManager searchMan = applicationContext.getBean(EsSearchManager.class);

String esFieldName = "resourceTitleObject.\\\\*.keyword";
if (metadataField.equals("altTitle")) {
esFieldName = "resourceAltTitleObject.\\\\*.keyword";
} else if (metadataField.equals("identifier")) {
esFieldName = "resourceIdentifier.code";
}

boolean duplicatedMetadataValue = false;
String jsonQuery = " {" +
" \"query_string\": {" +
" \"query\": \"+" + esFieldName + ":\\\"%s\\\" -uuid:\\\"%s\\\"\"" +
" }" +
"}";

ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode esJsonQuery = objectMapper.readTree(String.format(jsonQuery, metadataValue, metadataUuidToExclude));

final SearchResponse queryResult = searchMan.query(
esJsonQuery,
FIELDLIST_UUID,
0, 5);

duplicatedMetadataValue = (queryResult.getHits().getTotalHits().value > 0);
} catch (Exception ex) {
Log.error(API.LOG_MODULE_NAME, ex.getMessage(), ex);
}
return duplicatedMetadataValue;
}

/**
* Checks if a result for a search query has results.
Expand Down
102 changes: 102 additions & 0 deletions web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,4 +541,106 @@
};
}
]);

/**
* @ngdoc directive
* @name gn_fields.directive:gnDuplicatedMetadataValueChecker
*
* @description
* Checks if the associated control value exists in another metadata record.
* Valid field keys:
* - title: Metadata title.
* - altTitle: Metadata alternative title.
* - identifier: Metadata resource identifier.
* Configure in your metadata schema config-editor.xml the usage of this directive
* for the title element. For example, for iso19139:
* <fields>
* ...
* <for name="gmd:alternateTitle" use="data-gn-duplicated-metadata-value-checker">
* <directiveAttributes
* data-field-name="ResourceName"
* data-field-key="altTitle" />
*/
module.directive("gnDuplicatedMetadataValueChecker", [
"gnCurrentEdit",
"$http",
"$compile",
"$translate",
function (gnCurrentEdit, $http, $compile, $translate) {
return {
restrict: "A",
scope: {
fieldKey: "@" // Elasticsearch field name. Allowed values: title (Metadata title), altTitle (Metadata alternate title), identifier (Resource identifier)
},
link: function (scope, element, attrs) {
var duplicatedFieldNameMessage = $translate.instant(
"metadataDuplicatedField-" + scope.fieldKey
);

var messageTemplate =
"<p class='help-block' " +
"style='color: #d9534f' " +
"data-ng-show='duplicatedValue && !hiddenControl' " +
"data-translate>" +
duplicatedFieldNameMessage +
"</p>";
var messageTemplateCompiled = $compile(messageTemplate)(scope);

var messageTarget = document.getElementById(element[0].id);
element.blur(function () {
if (messageTarget.value !== scope.metadataFieldValue) {
scope.metadataFieldValue = messageTarget.value;
scope.checkField(scope.metadataFieldValue, scope.metadataUuid);
}
});

scope.metadataUuid = gnCurrentEdit.uuid;
scope.metadataFieldValue = messageTarget.value;
scope.duplicatedValue = false;
scope.hiddenControl = false;

element.after(messageTemplateCompiled);

// For multilingual title directive to hide the messages when displaying each language individually
var observer = new MutationObserver(function (event) {
if (event.length > 0) {
scope.hiddenControl = event[0].target.className.indexOf("hidden") > -1;
// Force a refresh, otherwise takes a delay to show / hide the message
scope.$apply();
}
});

observer.observe(messageTarget, {
attributes: true,
attributeFilter: ["class"],
childList: false,
characterData: false
});

scope.checkField = function (fieldValue, metadataUuid) {
if (fieldValue === "") {
scope.duplicatedValue = false;
return;
}

var postBody = {
field: scope.fieldKey,
value: fieldValue
};

$http
.post(
"../api/records/" + metadataUuid + "/checkDuplicatedFieldValue",
postBody
)
.then(function (response) {
scope.duplicatedValue = response.data === true;
});
};

scope.checkField(scope.metadataFieldValue, scope.metadataUuid);
}
};
}
]);
})();
5 changes: 4 additions & 1 deletion web-ui/src/main/resources/catalog/locales/en-editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -429,5 +429,8 @@
"wmsSelectedLayers": "Selected layers",
"wmsSelectedLayersNone": "No layers selected",
"validationSuccessLabel": "success",
"validationErrorLabel": "errors"
"validationErrorLabel": "errors",
"metadataDuplicatedField-title": "The metadata title is used in another metadata record.",
"metadataDuplicatedField-altTitle": "The metadata alternate title is used in another metadata record.",
"metadataDuplicatedField-identifier": "The metadata resource identifier is used in another metadata record."
}

0 comments on commit 7f8663b

Please sign in to comment.