From bd0d45368fcca84ffeec94daaa4bfc7b88167df8 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <p.gawron@atcomp.pl>
Date: Thu, 13 Mar 2025 12:59:08 +0100
Subject: [PATCH] add source data to data overlay entry

---
 CHANGELOG                                     |  5 ++
 .../converter/ColorSchemaColumn.java          | 61 +++++++++--------
 .../converter/ColorSchemaReader.java          | 67 +++++++++++--------
 .../converter/ColorSchemaReaderTest.java      | 50 ++++++++------
 .../model/overlay/DataOverlayEntry.java       | 10 +++
 ...0313__add_source_data_to_overlay_entry.sql |  2 +
 ...0313__add_source_data_to_overlay_entry.sql |  2 +
 .../overlays/OverlayEntryDTOSerializer.java   | 19 +++---
 .../web/OverlayControllerIntegrationTest.java | 30 +++++++++
 .../lcsb/mapviewer/web/api/NewApiDocs.java    |  4 ++
 10 files changed, 164 insertions(+), 86 deletions(-)
 create mode 100644 persist/src/main/resources/db/migration/hsql/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql
 create mode 100644 persist/src/main/resources/db/migration/postgres/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql

diff --git a/CHANGELOG b/CHANGELOG
index dd576dddf6..f741b55be8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+minerva (18.2.0) stable; urgency=medium
+  * Small improvement: add source data to data overlay (#2228)
+
+ -- Piotr Gawron <piotr.gawron@uni.lu>  Thu, 13 Mar 2025 13:00:00 +0200
+
 minerva (18.1.1) stable; urgency=medium
   * Bug fix: SBGN-ML import/export should not use compartmentOrder for
     non-compartment entities (#2193)
diff --git a/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaColumn.java b/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaColumn.java
index 8ff5134345..109c1d7ba4 100644
--- a/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaColumn.java
+++ b/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaColumn.java
@@ -1,124 +1,123 @@
 package lcsb.mapviewer.converter;
 
+import lcsb.mapviewer.model.overlay.DataOverlayType;
+
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
-import lcsb.mapviewer.model.overlay.DataOverlayType;
-
 /**
  * This enum defines which columns are available for defining
  * {@link lcsb.mapviewer.model.map.layout.ColorSchema}.
- * 
+ *
  * @author Piotr Gawron
- * 
  */
 public enum ColorSchemaColumn {
 
   /**
    * Name of the element.
    */
-  NAME(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  NAME(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
-  GENE_NAME(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  GENE_NAME(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Name of the map.
    */
-  MAP_NAME(new DataOverlayType[] { DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT }),
+  MAP_NAME(new DataOverlayType[]{DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT}),
+
+  SOURCE_DATA(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * Value that will be transformed into new color.
-   * 
+   *
    * @see ColorSchemaColumn#COLOR
    */
-  VALUE(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  VALUE(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * In which compartment the element should be located.
    */
-  COMPARTMENT(new DataOverlayType[] { DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT }),
+  COMPARTMENT(new DataOverlayType[]{DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Class type of the element.
    */
-  TYPE(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  TYPE(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * New element/reaction color.
    */
-  COLOR(new DataOverlayType[] { DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT }),
+  COLOR(new DataOverlayType[]{DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Identifier of the element.
    */
-  IDENTIFIER(new DataOverlayType[] { DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT }),
+  IDENTIFIER(new DataOverlayType[]{DataOverlayType.GENERIC, DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Element identifier.
    */
-  ELEMENT_IDENTIFIER(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  ELEMENT_IDENTIFIER(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * New line width of the reaction.
    */
-  LINE_WIDTH(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  LINE_WIDTH(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * Position where gene variants starts.
    */
-  POSITION(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  POSITION(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Original DNA of the variant.
    */
-  ORIGINAL_DNA(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  ORIGINAL_DNA(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Alternative DNA of the variant.
    */
-  ALTERNATIVE_DNA(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  ALTERNATIVE_DNA(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Short description of the entry.
    */
-  DESCRIPTION(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT, DataOverlayType.GENERIC }),
+  DESCRIPTION(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT, DataOverlayType.GENERIC}),
 
   /**
    * Contig where variant was observed.
    */
-  CONTIG(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  CONTIG(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
-  CHROMOSOME(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  CHROMOSOME(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
-  ALLELE_FREQUENCY(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  ALLELE_FREQUENCY(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
-  VARIANT_IDENTIFIER(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT }),
+  VARIANT_IDENTIFIER(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT}),
 
   /**
    * Should the direction of reaction be reversed.
    */
-  REVERSE_REACTION(new DataOverlayType[] { DataOverlayType.GENERIC }),
+  REVERSE_REACTION(new DataOverlayType[]{DataOverlayType.GENERIC}),
 
   /**
    * Optional amino acid change in the variant.
    */
-  AMINO_ACID_CHANGE(new DataOverlayType[] { DataOverlayType.GENETIC_VARIANT });
+  AMINO_ACID_CHANGE(new DataOverlayType[]{DataOverlayType.GENETIC_VARIANT});
 
   /**
    * Set of types where column is allowed.
    */
-  private Set<DataOverlayType> types = new HashSet<>();
+  private final Set<DataOverlayType> types = new HashSet<>();
 
   /**
    * Default constructor that creates enum entry.
    *
-   * @param types
-   *          list of {@link DataOverlayType types} where this column is allowed
+   * @param types list of {@link DataOverlayType types} where this column is allowed
    */
   ColorSchemaColumn(final DataOverlayType[] types) {
-    for (final DataOverlayType colorSchemaType : types) {
-      this.types.add(colorSchemaType);
-    }
+    Collections.addAll(this.types, types);
   }
 
   public String getColumnName() {
diff --git a/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaReader.java b/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaReader.java
index 03c368cd93..f6eef6f01d 100644
--- a/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaReader.java
+++ b/converter/src/main/java/lcsb/mapviewer/converter/ColorSchemaReader.java
@@ -52,9 +52,9 @@ import lcsb.mapviewer.model.overlay.InvalidDataOverlayException;
 /**
  * Class that reads information about set of {@link DataOverlayEntry color
  * schemas} from the input file.
- * 
+ *
  * @author Piotr Gawron
- * 
+ *
  */
 public class ColorSchemaReader {
 
@@ -104,7 +104,7 @@ public class ColorSchemaReader {
   /**
    * Reads information about set of {@link DataOverlayEntry color schemas} from
    * the input stream.
-   * 
+   *
    * @param colorInputStream
    *          input stream with {@link DataOverlayEntry}
    * @param params
@@ -150,7 +150,7 @@ public class ColorSchemaReader {
   /**
    * Reads information about set of {@link DataOverlayEntry color schemas} from
    * the filename.
-   * 
+   *
    * @param filename
    *          file with {@link DataOverlayEntry}
    * @return list of coloring schemas
@@ -172,7 +172,7 @@ public class ColorSchemaReader {
   /**
    * Reads information about set of {@link DataOverlayEntry color schemas} from
    * the input stream represented as byte array.
-   * 
+   *
    * @param inputData
    *          source in form of byte array
    * @return list of coloring schemas
@@ -198,7 +198,7 @@ public class ColorSchemaReader {
   /**
    * Reads information about set of {@link GeneVariantDataOverlayEntry gene
    * variant color schemas} from the input stream.
-   * 
+   *
    * @param colorInputStream
    *          input stream with {@link GeneVariantDataOverlayEntry}
    * @param params
@@ -372,7 +372,7 @@ public class ColorSchemaReader {
   /**
    * Sets proper value of identifier to {@link DataOverlayEntry} from cell
    * content.
-   * 
+   *
    * @param schema
    *          {@link DataOverlayEntry} where name should be set
    * @param type
@@ -388,7 +388,7 @@ public class ColorSchemaReader {
 
   /**
    * Sets proper name to {@link DataOverlayEntry} from cell content.
-   * 
+   *
    * @param schema
    *          {@link DataOverlayEntry} where name should be set
    * @param content
@@ -406,10 +406,17 @@ public class ColorSchemaReader {
     }
   }
 
+
+  private void processSourceDataColumn(final DataOverlayEntry schema, final String content) {
+    if (!content.isEmpty()) {
+      schema.setSourceData(content);
+    }
+  }
+
   /**
    * Sets proper compartment names to {@link DataOverlayEntry} from cell
    * content.
-   * 
+   *
    * @param schema
    *          {@link DataOverlayEntry} where name should be set
    * @param content
@@ -425,7 +432,7 @@ public class ColorSchemaReader {
 
   /**
    * Transforms string into {@link ReferenceGenomeType}.
-   * 
+   *
    * @param referenceGenomeStr
    *          type as a string
    * @return {@link ReferenceGenomeType} obtained from input string
@@ -450,13 +457,13 @@ public class ColorSchemaReader {
   /**
    * Merges collection of {@link DataOverlayEntry} that might contain duplicate
    * names into collection that doesn't have duplicate names.
-   * 
+   *
    * @param schemas
    *          {@link Collection} of {@link DataOverlayEntry} that might contain
    *          duplicate name
    * @return {@link Collection} of {@link DataOverlayEntry} that doesn't contain
    *         duplicate names
-   * 
+   *
    */
   private Collection<DataOverlayEntry> mergeSchemas(final Collection<DataOverlayEntry> schemas) {
     Map<String, DataOverlayEntry> schemasByName = new HashMap<>();
@@ -500,7 +507,7 @@ public class ColorSchemaReader {
 
   /**
    * Gets color that should be assigned to {@link GeneVariantDataOverlayEntry}.
-   * 
+   *
    * @param geneVariations
    *          list of variants
    * @return {@link Color} that should be assigned to
@@ -519,7 +526,7 @@ public class ColorSchemaReader {
   /**
    * Reads information about set of {@link GenericDataOverlayEntry generic color
    * schemas} from the input stream.
-   * 
+   *
    * @param colorInputStream
    *          input stream with {@link GenericDataOverlayEntry}
    * @return list of coloring schemas
@@ -549,17 +556,18 @@ public class ColorSchemaReader {
       Map<ColorSchemaColumn, Integer> schemaColumns = new HashMap<>();
       List<Pair<MiriamType, Integer>> customIdentifiers = parseColumns(columns, schemaColumns, DataOverlayType.GENERIC);
 
-      Integer valueColumn = schemaColumns.get(ColorSchemaColumn.VALUE);
-      Integer colorColumn = schemaColumns.get(ColorSchemaColumn.COLOR);
-      Integer nameColumn = schemaColumns.get(ColorSchemaColumn.NAME);
-      Integer modelNameColumn = schemaColumns.get(ColorSchemaColumn.MAP_NAME);
-      Integer identifierColumn = schemaColumns.get(ColorSchemaColumn.IDENTIFIER);
-      Integer elementIdentifierColumn = schemaColumns.get(ColorSchemaColumn.ELEMENT_IDENTIFIER);
-      Integer compartmentColumn = schemaColumns.get(ColorSchemaColumn.COMPARTMENT);
-      Integer typeColumn = schemaColumns.get(ColorSchemaColumn.TYPE);
-      Integer lineWidthColumn = schemaColumns.get(ColorSchemaColumn.LINE_WIDTH);
-      Integer reverseReactionColumn = schemaColumns.get(ColorSchemaColumn.REVERSE_REACTION);
-      Integer descriptionColumn = schemaColumns.get(ColorSchemaColumn.DESCRIPTION);
+      final Integer valueColumn = schemaColumns.get(ColorSchemaColumn.VALUE);
+      final Integer colorColumn = schemaColumns.get(ColorSchemaColumn.COLOR);
+      final Integer nameColumn = schemaColumns.get(ColorSchemaColumn.NAME);
+      final Integer modelNameColumn = schemaColumns.get(ColorSchemaColumn.MAP_NAME);
+      final Integer sourceDataColumn = schemaColumns.get(ColorSchemaColumn.SOURCE_DATA);
+      final Integer identifierColumn = schemaColumns.get(ColorSchemaColumn.IDENTIFIER);
+      final Integer elementIdentifierColumn = schemaColumns.get(ColorSchemaColumn.ELEMENT_IDENTIFIER);
+      final Integer compartmentColumn = schemaColumns.get(ColorSchemaColumn.COMPARTMENT);
+      final Integer typeColumn = schemaColumns.get(ColorSchemaColumn.TYPE);
+      final Integer lineWidthColumn = schemaColumns.get(ColorSchemaColumn.LINE_WIDTH);
+      final Integer reverseReactionColumn = schemaColumns.get(ColorSchemaColumn.REVERSE_REACTION);
+      final Integer descriptionColumn = schemaColumns.get(ColorSchemaColumn.DESCRIPTION);
 
       if (nameColumn == null && identifierColumn == null && customIdentifiers.size() == 0
           && elementIdentifierColumn == null) {
@@ -594,6 +602,9 @@ public class ColorSchemaReader {
         if (modelNameColumn != null) {
           processModelNameColumn(schema, values[modelNameColumn]);
         }
+        if (sourceDataColumn != null) {
+          processSourceDataColumn(schema, values[sourceDataColumn]);
+        }
         if (valueColumn != null) {
           schema.setValue(parseValueColumn(values[valueColumn], errorPrefix));
         }
@@ -715,7 +726,7 @@ public class ColorSchemaReader {
   /**
    * Sets proper reaction identifier to {@link DataOverlayEntry} from cell
    * content.
-   * 
+   *
    * @param schema
    *          {@link DataOverlayEntry} where name should be set
    * @param content
@@ -731,7 +742,7 @@ public class ColorSchemaReader {
   /**
    * Transform headers of columns into map with {@link ColorSchemaColumn column
    * types} to column number.
-   * 
+   *
    * @param columns
    *          headers of columns
    * @param schemaColumns
@@ -787,7 +798,7 @@ public class ColorSchemaReader {
 
   /**
    * Returns list of columns that should be printed for given coloring schemas.
-   * 
+   *
    * @param schemas
    *          list of schemas
    * @return list of columns that should be printed (were set in the coloring
diff --git a/converter/src/test/java/lcsb/mapviewer/converter/ColorSchemaReaderTest.java b/converter/src/test/java/lcsb/mapviewer/converter/ColorSchemaReaderTest.java
index 26653afab2..e6a507c1f9 100644
--- a/converter/src/test/java/lcsb/mapviewer/converter/ColorSchemaReaderTest.java
+++ b/converter/src/test/java/lcsb/mapviewer/converter/ColorSchemaReaderTest.java
@@ -1,15 +1,22 @@
 package lcsb.mapviewer.converter;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import lcsb.mapviewer.common.Configuration;
+import lcsb.mapviewer.common.TextFileUtils;
+import lcsb.mapviewer.model.map.BioEntity;
+import lcsb.mapviewer.model.map.MiriamData;
+import lcsb.mapviewer.model.overlay.DataOverlayEntry;
+import lcsb.mapviewer.model.overlay.GeneVariant;
+import lcsb.mapviewer.model.overlay.GeneVariantDataOverlayEntry;
+import lcsb.mapviewer.model.overlay.InvalidDataOverlayException;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.awt.Color;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -18,19 +25,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import lcsb.mapviewer.common.Configuration;
-import lcsb.mapviewer.common.TextFileUtils;
-import lcsb.mapviewer.model.map.BioEntity;
-import lcsb.mapviewer.model.map.MiriamData;
-import lcsb.mapviewer.model.overlay.DataOverlayEntry;
-import lcsb.mapviewer.model.overlay.GeneVariant;
-import lcsb.mapviewer.model.overlay.GeneVariantDataOverlayEntry;
-import lcsb.mapviewer.model.overlay.InvalidDataOverlayException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 public class ColorSchemaReaderTest extends ConverterTestFunctions {
 
@@ -89,7 +87,7 @@ public class ColorSchemaReaderTest extends ConverterTestFunctions {
     assertEquals(1, schemas.size());
   }
 
-  private byte[] fileToByteArray(final File f) throws FileNotFoundException, IOException {
+  private byte[] fileToByteArray(final File f) throws IOException {
     InputStream in = new FileInputStream(f);
 
     byte[] buff = new byte[8000];
@@ -124,6 +122,7 @@ public class ColorSchemaReaderTest extends ConverterTestFunctions {
     for (final GeneVariant geneVariant : schema.getGeneVariants()) {
       if (geneVariant.getAminoAcidChange() != null && !geneVariant.getAminoAcidChange().trim().isEmpty()) {
         aaChangeFound = true;
+        break;
       }
     }
     assertTrue("Amino acid are not present in parsed data", aaChangeFound);
@@ -362,4 +361,17 @@ public class ColorSchemaReaderTest extends ConverterTestFunctions {
     assertEquals(2, schemas.size());
   }
 
+  @Test
+  public void testColoringWithSourceData() throws Exception {
+    String input = "name\tcolor\tsource_data\n"
+        + "s1\t#ff0000\txxx\n";
+    Map<String, String> params = new HashMap<>();
+    params.put(TextFileUtils.COLUMN_COUNT_PARAM, "3");
+
+    Collection<DataOverlayEntry> schemas = reader
+        .readColorSchema(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), params);
+    assertEquals(1, schemas.size());
+    assertNotNull(schemas.iterator().next().getSourceData());
+  }
+
 }
diff --git a/model/src/main/java/lcsb/mapviewer/model/overlay/DataOverlayEntry.java b/model/src/main/java/lcsb/mapviewer/model/overlay/DataOverlayEntry.java
index ed60700108..db715cc745 100644
--- a/model/src/main/java/lcsb/mapviewer/model/overlay/DataOverlayEntry.java
+++ b/model/src/main/java/lcsb/mapviewer/model/overlay/DataOverlayEntry.java
@@ -60,6 +60,8 @@ public abstract class DataOverlayEntry implements Serializable {
    */
   private String modelName = null;
 
+  private String sourceData = null;
+
   /**
    * Original identifier of the {@link BioEntity} to change the color.
    */
@@ -415,4 +417,12 @@ public abstract class DataOverlayEntry implements Serializable {
   public void setDataOverlay(final DataOverlay dataOverlay) {
     this.dataOverlay = dataOverlay;
   }
+
+  public String getSourceData() {
+    return sourceData;
+  }
+
+  public void setSourceData(final String sourceData) {
+    this.sourceData = sourceData;
+  }
 }
diff --git a/persist/src/main/resources/db/migration/hsql/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql b/persist/src/main/resources/db/migration/hsql/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql
new file mode 100644
index 0000000000..93bdf335b4
--- /dev/null
+++ b/persist/src/main/resources/db/migration/hsql/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql
@@ -0,0 +1,2 @@
+alter table data_overlay_entry_table
+    add column source_data character varying(255);
diff --git a/persist/src/main/resources/db/migration/postgres/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql b/persist/src/main/resources/db/migration/postgres/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql
new file mode 100644
index 0000000000..93bdf335b4
--- /dev/null
+++ b/persist/src/main/resources/db/migration/postgres/18.2.0/V18.0.8.20250313__add_source_data_to_overlay_entry.sql
@@ -0,0 +1,2 @@
+alter table data_overlay_entry_table
+    add column source_data character varying(255);
diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayEntryDTOSerializer.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayEntryDTOSerializer.java
index 64932af3e8..99fb059d98 100644
--- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayEntryDTOSerializer.java
+++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayEntryDTOSerializer.java
@@ -1,27 +1,26 @@
 package lcsb.mapviewer.api.projects.overlays;
 
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.ser.FilterProvider;
 import com.fasterxml.jackson.databind.ser.PropertyFilter;
-
 import lcsb.mapviewer.api.projects.overlays.OverlayController.OverlayEntryDTO;
 import lcsb.mapviewer.model.overlay.DataOverlayType;
 import lcsb.mapviewer.model.overlay.GeneVariantDataOverlayEntry;
 import lcsb.mapviewer.modelutils.serializer.CustomExceptFilter;
 import lcsb.mapviewer.modelutils.serializer.model.map.ElementIdentifierType;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
 public class OverlayEntryDTOSerializer extends JsonSerializer<OverlayEntryDTO> {
 
   @Override
   public void serialize(final OverlayEntryDTO entry, final JsonGenerator gen,
-      final SerializerProvider serializers)
+                        final SerializerProvider serializers)
       throws IOException {
 
     PropertyFilter filter = null;
@@ -60,6 +59,9 @@ public class OverlayEntryDTOSerializer extends JsonSerializer<OverlayEntryDTO> {
         case "description":
           value = entry.getEntry().getDescription();
           break;
+        case "sourcedata":
+          value = entry.getEntry().getSourceData();
+          break;
         case "width":
           if (Objects.equals(entry.getType(), ElementIdentifierType.REACTION)) {
             value = entry.getEntry().getLineWidth();
@@ -78,7 +80,7 @@ public class OverlayEntryDTOSerializer extends JsonSerializer<OverlayEntryDTO> {
           if (entry.getEntry() instanceof GeneVariantDataOverlayEntry) {
             value = ((GeneVariantDataOverlayEntry) entry.getEntry()).getGeneVariants();
           } else {
-            value = new Object[] {};
+            value = new Object[]{};
           }
           break;
         default:
@@ -102,6 +104,7 @@ public class OverlayEntryDTOSerializer extends JsonSerializer<OverlayEntryDTO> {
         "type",
         "geneVariations",
         "uniqueId",
+        "sourceData",
         "width");
   }
 
diff --git a/web/src/test/java/lcsb/mapviewer/web/OverlayControllerIntegrationTest.java b/web/src/test/java/lcsb/mapviewer/web/OverlayControllerIntegrationTest.java
index 88fe296228..7733dc5cda 100644
--- a/web/src/test/java/lcsb/mapviewer/web/OverlayControllerIntegrationTest.java
+++ b/web/src/test/java/lcsb/mapviewer/web/OverlayControllerIntegrationTest.java
@@ -631,6 +631,32 @@ public class OverlayControllerIntegrationTest extends ControllerIntegrationTest
     assertEquals(1, dataOverlayService.getDataOverlaysByProject(project).size());
   }
 
+  @Test
+  public void testAdminCreateOverlayWithSourceData() throws Exception {
+    final User admin = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN);
+
+    final UploadedFileEntry file = createFile("element_identifier\tvalue\tsource_data\n\t-1\txxx", admin);
+
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    final String body = EntityUtils.toString(new UrlEncodedFormEntity(Arrays.asList(
+        new BasicNameValuePair("fileId", String.valueOf(file.getId())),
+        new BasicNameValuePair("name", "overlay name"),
+        new BasicNameValuePair("description", "overlay name"),
+        new BasicNameValuePair("filename", "overlay name"),
+        new BasicNameValuePair("type", "GENERIC"))));
+
+    final RequestBuilder request = post("/minerva/api/projects/{projectId}/overlays/", TEST_PROJECT)
+        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+        .content(body)
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().is2xxSuccessful());
+
+    assertEquals(1, dataOverlayService.getDataOverlaysByProject(project).size());
+  }
+
   @Test
   public void testAdminCreateOverlayFromContent() throws Exception {
     final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
@@ -1448,6 +1474,10 @@ public class OverlayControllerIntegrationTest extends ControllerIntegrationTest
             .description("description assigned to the bioEntity from overlay")
             .type(JsonFieldType.STRING)
             .optional(),
+        fieldWithPath("overlayContent.sourceData")
+            .description("source data")
+            .type(JsonFieldType.STRING)
+            .optional(),
         fieldWithPath("overlayContent.geneVariations")
             .description("list of gene variants assigned to the bioEntity from overlay")
             .type(JsonFieldType.ARRAY)
diff --git a/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java b/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
index 13293284c3..72b1da5f35 100644
--- a/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
+++ b/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
@@ -129,6 +129,10 @@ public class NewApiDocs {
         fieldWithPath(prefix + "name")
             .description("bioEntity name that should match")
             .type(JsonFieldType.STRING),
+        fieldWithPath(prefix + "sourceData")
+            .description("source data")
+            .optional()
+            .type(JsonFieldType.STRING),
         fieldWithPath(prefix + "modelName")
             .description("bioEntity model name that should match")
             .optional()
-- 
GitLab