From 071cc95afb6d5e5972ae5f156e83e7b38599571e Mon Sep 17 00:00:00 2001 From: Miriam Baglioni Date: Fri, 20 Dec 2024 14:41:09 +0100 Subject: [PATCH] [orcipPropagation] test to verify the merge for multiple enrichment from multiple sources. It covers also the check for persistency of other identifiers types --- .../author/SparkEnrichWithOrcidAuthors.scala | 8 +- .../SparkPropagateOrcidAuthor.java | 71 +----------------- .../OrcidPropagationJobTest.java | 33 +++++--- .../sample/oneupdate/dataset_10.json.gz | Bin 6895 -> 0 bytes .../sample/twoupdates/dataset/dataset_10.json | 10 +++ .../sample/twoupdates/dataset_10.json.gz | Bin 6913 -> 0 bytes .../otherresearchproduct/empty.json | 0 .../sample/twoupdates/publication/empty.json | 0 .../sample/twoupdates/relation/relation.json | 9 +++ .../sample/twoupdates/software/empty.json | 0 10 files changed, 46 insertions(+), 85 deletions(-) delete mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/oneupdate/dataset_10.json.gz create mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/dataset/dataset_10.json delete mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/dataset_10.json.gz create mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/otherresearchproduct/empty.json create mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/publication/empty.json create mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/relation/relation.json create mode 100644 dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates/software/empty.json diff --git a/dhp-common/src/main/scala/eu/dnetlib/dhp/common/author/SparkEnrichWithOrcidAuthors.scala b/dhp-common/src/main/scala/eu/dnetlib/dhp/common/author/SparkEnrichWithOrcidAuthors.scala index 01dc4c9dd..f3727ee4c 100644 --- a/dhp-common/src/main/scala/eu/dnetlib/dhp/common/author/SparkEnrichWithOrcidAuthors.scala +++ b/dhp-common/src/main/scala/eu/dnetlib/dhp/common/author/SparkEnrichWithOrcidAuthors.scala @@ -9,11 +9,7 @@ import org.slf4j.{Logger, LoggerFactory} import eu.dnetlib.dhp.common.enrichment.Constants.PROPAGATION_DATA_INFO_TYPE import eu.dnetlib.dhp.schema.oaf.{OafEntity, Result} import eu.dnetlib.dhp.schema.oaf.utils.MergeUtils -import org.apache.spark.api.java.function.{MapFunction, MapGroupsFunction} -import org.apache.spark.sql.expressions.Aggregator -import java.util -import java.util.Iterator import scala.collection.JavaConverters._ abstract class SparkEnrichWithOrcidAuthors(propertyPath: String, args: Array[String], log: Logger) @@ -83,7 +79,8 @@ abstract class SparkEnrichWithOrcidAuthors(propertyPath: String, args: Array[Str .mapGroups((k, it) => { val p: Result = it.next - it.foldLeft(p.getAuthor)((x,r) => MergeUtils.mergeAuthors(x, r.getAuthor,0)) + p.setAuthor(it.foldLeft(p.getAuthor)((x,r) => MergeUtils.mergeAuthors(x, r.getAuthor,0))) + p })(Encoders.bean(resultClazz)) @@ -94,7 +91,6 @@ abstract class SparkEnrichWithOrcidAuthors(propertyPath: String, args: Array[Str }) } - // def generateGraph(spark: SparkSession, graphPath: String, workingDir: String, targetPath: String): Unit def createTemporaryData(spark: SparkSession, graphPath: String, orcidPath: String, targetPath: String): Unit diff --git a/dhp-workflows/dhp-enrichment/src/main/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/SparkPropagateOrcidAuthor.java b/dhp-workflows/dhp-enrichment/src/main/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/SparkPropagateOrcidAuthor.java index bedfc93fd..9fe50d707 100644 --- a/dhp-workflows/dhp-enrichment/src/main/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/SparkPropagateOrcidAuthor.java +++ b/dhp-workflows/dhp-enrichment/src/main/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/SparkPropagateOrcidAuthor.java @@ -6,14 +6,9 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.schema.oaf.utils.MergeUtils; -import eu.dnetlib.dhp.utils.ORCIDAuthorEnricherResult; import org.apache.spark.api.java.function.FilterFunction; -import org.apache.spark.api.java.function.ForeachFunction; import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.*; import org.apache.spark.sql.Dataset; import org.slf4j.Logger; @@ -24,7 +19,7 @@ import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.utils.OrcidAuthor; import scala.Tuple2; -import static org.apache.spark.sql.functions.*; + public class SparkPropagateOrcidAuthor extends SparkEnrichWithOrcidAuthors { private static final Logger log = LoggerFactory.getLogger(SparkPropagateOrcidAuthor.class); @@ -89,69 +84,6 @@ public class SparkPropagateOrcidAuthor extends SparkEnrichWithOrcidAuthors { } -// private void processAndMerge( -// SparkSession spark, -// String inputPath, -// String outputPath, -// Class clazz, -// Encoder encoder) { -// -// spark.read() -// .schema(Encoders.bean(clazz).schema()) -// .json(inputPath) -// .as(encoder) -// .groupByKey((MapFunction) OafEntity::getId, Encoders.STRING()) -// .mapGroups((MapGroupsFunction) (k, it) -> { -// T p = it.next(); -// it.forEachRemaining(r -> p.setAuthor(MergeUtils.mergeAuthors(p.getAuthor(),r.getAuthor(),0))); -// return p; -// }, encoder) -// .write() -// .mode(SaveMode.Overwrite) -// .option("compression", "gzip") -// .json(outputPath); -// } -// @Override -//public void generateGraph(SparkSession spark, String graphPath, String workingDir, String targetPath){ -// -// ModelSupport.entityTypes.keySet().stream().filter(ModelSupport::isResult) -// .forEach(e -> { -// Class resultClazz = ModelSupport.entityTypes.get(e); -// Dataset matched = spark -// .read() -// .schema(Encoders.bean(ORCIDAuthorEnricherResult.class).schema()) -// .parquet(workingDir + "/" + e.name() + "_matched") -// .selectExpr("id","enriched_author"); -// Dataset result = spark.read().schema(Encoders.bean(resultClazz).schema()) -// .json(graphPath + "/" + e.name()); -// -// -// -// result.join(matched, result.col("id").equalTo(matched.col("id")), "left") -// .withColumn( -// "author", -// when(size(col("enriched_author")).gt(0), col("enriched_author")) -// .otherwise(col("author")) -// ) -// .drop(matched.col("id")) -// .drop("enriched_author") -// .write() -// .mode(SaveMode.Overwrite) -// .option("compression", "gzip") -// .json(workingDir + "/" + e.name() + "/_tobemerged") -// ; -// processAndMerge( -// spark, -// workingDir + "/" + e.name() + "/_tobemerged", -// targetPath + "/" + e.name(), -// resultClazz, -// Encoders.bean(resultClazz) -// ); -// }); -// -// -// } - @Override public void createTemporaryData(SparkSession spark, String graphPath, String orcidPath, String targetPath) { Dataset supplements = spark @@ -163,7 +95,6 @@ public class SparkPropagateOrcidAuthor extends SparkEnrichWithOrcidAuthors { ModelConstants.IS_SUPPLEMENTED_BY + "')") .selectExpr("source as id", "target"); - Dataset result = spark .read() .schema(Encoders.bean(Result.class).schema()) diff --git a/dhp-workflows/dhp-enrichment/src/test/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/OrcidPropagationJobTest.java b/dhp-workflows/dhp-enrichment/src/test/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/OrcidPropagationJobTest.java index 46896c13a..642463a18 100644 --- a/dhp-workflows/dhp-enrichment/src/test/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/OrcidPropagationJobTest.java +++ b/dhp-workflows/dhp-enrichment/src/test/java/eu/dnetlib/dhp/orcidtoresultfromsemrel/OrcidPropagationJobTest.java @@ -183,16 +183,17 @@ public class OrcidPropagationJobTest { .getResource( "/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/twoupdates") .getPath(), - "-targetPath", - workingDir.toString() + "/dataset", - "-orcidPath", "", - "-workingDir", workingDir.toString() + "-targetPath", + workingDir.toString() + "/graph", + "-orcidPath", "", + "-workingDir", workingDir.toString(), + "-matchingSource", "propagation" }); final JavaSparkContext sc = new JavaSparkContext(spark.sparkContext()); JavaRDD tmp = sc - .textFile(workingDir.toString() + "/dataset") + .textFile(workingDir.toString() + "/graph/dataset") .map(item -> OBJECT_MAPPER.readValue(item, Dataset.class)); Assertions.assertEquals(10, tmp.count()); @@ -214,10 +215,10 @@ public class OrcidPropagationJobTest { Assertions .assertEquals( - 1, propagatedAuthors.filter("name = 'Marc' and surname = 'Schmidtmann'").count()); + 1, propagatedAuthors.filter("name = 'Nicole' and surname = 'Jung'").count()); Assertions .assertEquals( - 1, propagatedAuthors.filter("name = 'Ruediger' and surname = 'Beckhaus'").count()); + 1, propagatedAuthors.filter("name = 'Camilo José' and surname = 'Cela'").count()); query = "select id, MyT.name name, MyT.surname surname, MyP.value pid ,MyP.qualifier.classid pidType " + "from dataset " @@ -228,13 +229,27 @@ public class OrcidPropagationJobTest { Assertions .assertEquals( - 2, authorsExplodedPids.filter("name = 'Marc' and surname = 'Schmidtmann'").count()); + 3, authorsExplodedPids.filter("name = 'Camilo José' and surname = 'Cela'").count()); Assertions .assertEquals( 1, authorsExplodedPids .filter( - "name = 'Marc' and surname = 'Schmidtmann' and pidType = 'MAG Identifier'") + "name = 'Camilo José' and surname = 'Cela' and pidType = 'MAG Identifier'") .count()); + Assertions + .assertEquals( + 1, + authorsExplodedPids + .filter( + "name = 'Camilo José' and surname = 'Cela' and pidType = 'orcid'") + .count()); + Assertions + .assertEquals( + 1, + authorsExplodedPids + .filter( + "name = 'Camilo José' and surname = 'Cela' and pidType = 'orcid_pending'") + .count()); } } diff --git a/dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/oneupdate/dataset_10.json.gz b/dhp-workflows/dhp-enrichment/src/test/resources/eu/dnetlib/dhp/orcidtoresultfromsemrel/sample/oneupdate/dataset_10.json.gz deleted file mode 100644 index f4630a7a468a03347b3edb7142f133ff4ef538dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6895 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

cDb)|hIk83Jf4nl&vZH|aBzYwe* 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