Commit 038fd3b5 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch 'merge-14.0.4' into 'master'

Merge 14.0.4

See merge request minerva/core!1007
parents b61499bd c875e63c
......@@ -3,36 +3,50 @@ minerva (15.0.0~alpha.1) stable; urgency=medium
sessions (#836)
* Bug fix: structural states of proteins are imported properly from SBGNML PD
(#995)
* Bug fix: clear button icon, refresh comments button and show overview
* Bug fix: clear button icon, refresh comments button and show overview
button icons were missing (regression 15.0.0~alpha.0)
* Bug fix: export to CellDesigner names with new line is properly handled
(#930)
* Bug fix: layout of the exported reactome pathways is fixed when reactome
* Bug fix: layout of the exported reactome pathways is fixed when reactome
exporter violates SBGN specification (#707)
minerva (15.0.0~alpha.0) stable; urgency=medium
* Improvement: logs provided for validation data model are structurized (#325)
* Improvement: import/export of GPML implemented
* Small improvement: compartments in not layouted SBML file are more
scattered (#326)
scattered (#326)
* Small improvement: when downloading a map results in too big file (>1MB)
the content is compressed and returned as a zip file (#348)
* Small improvement: confirmation dialog when removing "general view"
overlays contain proper warning (#809)
* Small improvement: CellDesigner text area object can have "BorderColor"
* Small improvement: CellDesigner text area object can have "BorderColor"
property defined (#806)
* Small improvement: list of submaps is sorted alphabetically (#962)
* Small improvement: list of submaps is sorted alphabetically (#962)
* Small improvement: notification about new releases of minerva added in
admin panel (#961)
* Small improvement: font awesome upgraded to 5.1 (new icon styles)
* Small improvement: header contains login/logout button and access admin
panel button (#982)
* Bug fix: position of structural state is preserved on upload CellDesigner
* Bug fix: position of structural state is preserved on upload CellDesigner
file (#671)
* Bug fix: problematic notes doesn't crash CellDesigner upload (#968)
-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 4 Nov 2019 11:00:00 +0200
minerva (14.0.4) stable; urgency=high
* Bug fix: some SBGN files uploaded to minerva could not be exported to SBML
due to problems with identifiers used by SBGN (#1006)
* Bug fix: upload of data overlay with conflicting overlay types caused error
(#998)
* Bug fix: upload of sbml file with protein modifications inside complex
crashed visualization (#966)
* Bug fix: list of overlays for admin and curator included also other people
overlays (regression 14.0.3, #1008)
* Bug fix: refresh button did not refresh overlay list after overlay was
removed over API (#997)
-- Piotr Gawron <piotr.gawron@uni.lu> Thu, 07 Nov 2019 11:00:00 +0200
minerva (14.0.3) stable; urgency=medium
* Bug fix: default zoom level on main map works even when x or y are
undefined (#993)
......@@ -40,9 +54,9 @@ minerva (14.0.3) stable; urgency=medium
packages (#966)
* Bug fix: parsing of CellDesigner files that contained substanceUnits could
crash the upload (#985)
* Bug fix: verificatin of version length added when uploading project
* Bug fix: verification of version length added when uploading project
implemented (#978)
* Bug fix: CLEAR button disappeard when plugin tab used to much space (#976)
* Bug fix: CLEAR button disappeared when plugin tab used to much space (#976)
* Bug fix: removing data overlay refreshes list of overlays in info window
(#974)
* Bug fix: general overlays are always on top of data overlays in info window
......@@ -58,10 +72,10 @@ minerva (14.0.3) stable; urgency=medium
* Bug fix: fields in add project window are reinitialized after each open
(#963)
* Bug fix: filename case in uploaded zip files is preserved (#964)
* Bug fix: +/- buttons were misaligned in genemove browser (#942)
* Bug fix: +/- buttons were misaligned in genome browser (#942)
* Bug fix: color of links in the left panel was unintentionally changed
(#990)
* Bug fix: minerva logo in left panel is centered properly
* Bug fix: minerva logo in left panel is centred properly
* Bug fix: width of search button icon was too big
-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 18 Oct 2019 15:00:00 +0200
......
package lcsb.mapviewer.converter.model.sbml;
import java.awt.*;
import java.awt.Color;
import java.util.*;
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;
......@@ -10,10 +11,8 @@ import org.apache.logging.log4j.Logger;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.ext.SBasePlugin;
import org.sbml.jsbml.ext.layout.*;
import org.sbml.jsbml.ext.layout.Point;
import org.sbml.jsbml.ext.multi.MultiModelPlugin;
import org.sbml.jsbml.ext.render.*;
import org.sbml.jsbml.ext.render.Polygon;
import lcsb.mapviewer.common.XmlParser;
import lcsb.mapviewer.common.exception.InvalidStateException;
......@@ -49,12 +48,26 @@ public abstract class SbmlBioEntityExporter<T extends BioEntity, S extends org.s
private boolean provideDefaults;
private Pattern sbmlValidIdMatcher;
/**
* Map with elementId mapping for identifiers that cannot be used in sbml.
*/
private Map<String, String> correctedElementId = new HashMap<>();
public SbmlBioEntityExporter(Model sbmlModel, lcsb.mapviewer.model.map.model.Model minervaModel,
Collection<SbmlExtension> sbmlExtensions) {
this.sbmlModel = sbmlModel;
this.layout = getLayout(sbmlModel);
this.minervaModel = minervaModel;
this.sbmlExtensions.addAll(sbmlExtensions);
String underscore = "_";
String letter = "a-zA-Z";
String digit = "0-9";
String idChar = '[' + letter + digit + underscore + ']';
sbmlValidIdMatcher = Pattern.compile("^[" + letter + underscore + "]" + idChar + '*');
}
public void exportElements() throws InconsistentModelException {
......@@ -63,10 +76,10 @@ public abstract class SbmlBioEntityExporter<T extends BioEntity, S extends org.s
try {
S sbmlElement = getSbmlElement(bioEntity);
if (sbmlElementByElementId.get(bioEntity.getElementId()) != null) {
throw new InconsistentModelException("More than one species with id: " + bioEntity.getElementId());
if (sbmlElementByElementId.get(getElementId(bioEntity)) != null) {
throw new InconsistentModelException("More than one species with id: " + getElementId(bioEntity));
}
sbmlElementByElementId.put(bioEntity.getElementId(), sbmlElement);
sbmlElementByElementId.put(getElementId(bioEntity), sbmlElement);
} catch (Exception e) {
throw new InconsistentModelException(new ElementUtils().getElementTag(bioEntity) +
"Problem with exporting bioEntity", e);
......@@ -76,7 +89,7 @@ public abstract class SbmlBioEntityExporter<T extends BioEntity, S extends org.s
for (T bioEntity : speciesList) {
try {
AbstractReferenceGlyph elementGlyph = createGlyph(bioEntity);
sbmlGlyphByElementId.put(bioEntity.getElementId(), elementGlyph);
sbmlGlyphByElementId.put(getElementId(bioEntity), elementGlyph);
} catch (Exception e) {
throw new InconsistentModelException(new ElementUtils().getElementTag(bioEntity) +
"Problem with exporting bioEntity", e);
......@@ -127,8 +140,8 @@ public abstract class SbmlBioEntityExporter<T extends BioEntity, S extends org.s
protected abstract void assignLayoutToGlyph(T element, AbstractReferenceGlyph compartmentGlyph);
protected AbstractReferenceGlyph createGlyph(T element) {
String sbmlElementId = sbmlElementByElementId.get(element.getElementId()).getId();
String glyphId = element.getElementId();
String sbmlElementId = sbmlElementByElementId.get(getElementId(element)).getId();
String glyphId = getElementId(element);
AbstractReferenceGlyph elementGlyph = createElementGlyph(sbmlElementId, glyphId);
assignLayoutToGlyph(element, elementGlyph);
return elementGlyph;
......@@ -403,4 +416,19 @@ public abstract class SbmlBioEntityExporter<T extends BioEntity, S extends org.s
this.provideDefaults = provideDefaults;
}
public String getElementId(BioEntity bioEntity) {
String result = bioEntity.getElementId();
if (!sbmlValidIdMatcher.matcher(result).matches()) {
if (correctedElementId.get(result) == null) {
String newElementId = this.getClass().getSimpleName() + "_" + getNextId();
logger.warn(new ElementUtils().getElementTag(bioEntity) + " Element id cannot be used in sbml. Changing to: "
+ newElementId);
correctedElementId.put(result, newElementId);
}
result = correctedElementId.get(result);
}
return result;
}
}
......@@ -34,7 +34,7 @@ public class SbmlCompartmentExporter extends SbmlElementExporter<Compartment, or
result.addAll(getMinervaModel().getCompartments());
boolean defaultFound = false;
for (Compartment compartment : result) {
if (compartment.getElementId().equals("default")) {
if (getElementId(compartment).equals("default")) {
defaultFound = true;
}
}
......@@ -48,7 +48,7 @@ public class SbmlCompartmentExporter extends SbmlElementExporter<Compartment, or
@Override
public org.sbml.jsbml.Compartment createSbmlElement(Compartment element) throws InconsistentModelException {
org.sbml.jsbml.Compartment result;
if (element == null || element.getElementId().equals("default")) {
if (element == null || getElementId(element).equals("default")) {
result = getSbmlModel().createCompartment("default");
} else {
result = getSbmlModel().createCompartment("comp_" + (getNextId()));
......@@ -77,7 +77,7 @@ public class SbmlCompartmentExporter extends SbmlElementExporter<Compartment, or
@Override
protected String getSbmlIdKey(Compartment compartment) {
if (compartment == null || compartment.getElementId().equals("default")) {
if (compartment == null || getElementId(compartment).equals("default")) {
return "default";
}
return compartment.getName();
......@@ -131,7 +131,7 @@ public class SbmlCompartmentExporter extends SbmlElementExporter<Compartment, or
@Override
protected void assignLayoutToGlyph(Compartment element, AbstractReferenceGlyph speciesGlyph) {
if (element.getElementId().equals("default")) {
if (getElementId(element).equals("default")) {
BoundingBox boundingBox = new BoundingBox();
boundingBox.setPosition(new Point(element.getX(), element.getY(), 0));
......@@ -145,7 +145,7 @@ public class SbmlCompartmentExporter extends SbmlElementExporter<Compartment, or
speciesGlyph.setBoundingBox(boundingBox);
} else {
super.assignLayoutToGlyph(element, speciesGlyph);
TextGlyph textGlyph = getLayout().createTextGlyph("text_" + element.getElementId(), element.getName());
TextGlyph textGlyph = getLayout().createTextGlyph("text_" + getElementId(element), element.getName());
Point2D point = element.getNamePoint();
double width = element.getWidth() - (point.getX() - element.getX());
double height = element.getHeight() - (point.getY() - element.getY());
......
......@@ -98,11 +98,12 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
}
private String getReactionId(Reaction reaction) {
int separatorIndex = reaction.getElementId().indexOf("__");
String elementId = getElementId(reaction);
int separatorIndex = elementId.indexOf("__");
if (separatorIndex > 0) {
return reaction.getElementId().substring(0, separatorIndex);
return elementId.substring(0, separatorIndex);
}
return reaction.getElementId();
return elementId;
}
@Override
......@@ -124,19 +125,22 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
result.setSBOTerm(sboTerm);
}
for (Product product : reaction.getProducts()) {
Species sbmlSymbol = speciesExporter.getSbmlElementByElementId(product.getElement().getElementId());
Species sbmlSymbol = speciesExporter
.getSbmlElementByElementId(speciesExporter.getElementId(product.getElement()));
SpeciesReference speciesReference = result.createProduct(sbmlSymbol);
speciesReference.setConstant(false);
speciesReferenceByReactionNode.put(product, speciesReference);
}
for (Reactant reactant : reaction.getReactants()) {
Species sbmlSymbol = speciesExporter.getSbmlElementByElementId(reactant.getElement().getElementId());
Species sbmlSymbol = speciesExporter
.getSbmlElementByElementId(speciesExporter.getElementId(reactant.getElement()));
SpeciesReference speciesReference = result.createReactant(sbmlSymbol);
speciesReference.setConstant(false);
speciesReferenceByReactionNode.put(reactant, speciesReference);
}
for (Modifier modifier : reaction.getModifiers()) {
Species sbmlSymbol = speciesExporter.getSbmlElementByElementId(modifier.getElement().getElementId());
Species sbmlSymbol = speciesExporter
.getSbmlElementByElementId(speciesExporter.getElementId(modifier.getElement()));
SimpleSpeciesReference speciesReference = result.createModifier(sbmlSymbol);
speciesReferenceByReactionNode.put(modifier, speciesReference);
String term = SBOTermModifierType.getTermByType(modifier.getClass());
......@@ -151,8 +155,8 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
}
@Override
protected String getSbmlIdKey(Reaction element) {
return element.getClass().getSimpleName() + "\n" + element.getElementId();
protected String getSbmlIdKey(Reaction reaction) {
return reaction.getClass().getSimpleName() + "\n" + getElementId(reaction);
}
@Override
......@@ -367,7 +371,8 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
private SpeciesReferenceGlyph createNodeGlyph(ReactionGlyph reactionGlyph, ReactionNode node) {
SpeciesReferenceGlyph reactantGlyph = reactionGlyph.createSpeciesReferenceGlyph("node_" + getNextId());
reactantGlyph.setSpeciesGlyph(speciesExporter.getSbmlGlyphByElementId(node.getElement().getElementId()).getId());
reactantGlyph.setSpeciesGlyph(
speciesExporter.getSbmlGlyphByElementId(speciesExporter.getElementId(node.getElement())).getId());
Curve curve = createCurve(node.getLine(), node instanceof Reactant);
if (curve.getCurveSegmentCount() == 0) {
logger.warn(new ElementUtils().getElementTag(node) + " Problematic line");
......
......@@ -72,10 +72,10 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
Set<Reaction> used = new HashSet<>();
Map<String, Reaction> reactionById = new HashMap<>();
for (Reaction reaction : reactions) {
if (reactionById.get(reaction.getIdReaction()) != null) {
throw new InvalidInputDataExecption("Duplicated reaction id: " + reaction.getIdReaction());
if (reactionById.get(reaction.getElementId()) != null) {
throw new InvalidInputDataExecption("Duplicated reaction id: " + reaction.getElementId());
}
reactionById.put(reaction.getIdReaction(), reaction);
reactionById.put(reaction.getElementId(), reaction);
}
for (ReactionGlyph glyph : getLayout().getListOfReactionGlyphs()) {
......
......@@ -474,7 +474,7 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
createStructuralStateGlyph(element, speciesGlyph);
}
}
TextGlyph textGlyph = getLayout().createTextGlyph("text_" + element.getElementId(), element.getName());
TextGlyph textGlyph = getLayout().createTextGlyph("text_" + getElementId(element), element.getName());
textGlyph
.setBoundingBox(createBoundingBox(element.getX(), element.getY(), element.getZ() + 1, element.getWidth(),
element.getHeight()));
......
......@@ -613,11 +613,79 @@ public class SbmlExporterTest extends SbmlTestFunctions {
}
@Test
public void testExportInvalidMapId() throws Exception {
Model originalModel = createEmptyModel();
originalModel.setIdModel("F006-ACLY-SBGNv02.sbgn");
String result = exporter.toXml(originalModel);
assertNotNull(result);
public void testInvalidSpeciesId() throws Exception {
Model model = createEmptyModel();
GenericProtein p = createProtein();
p.setElementId("a-b");
model.addElement(p);
String xml = exporter.toXml(model);
assertNotNull(xml);
assertEquals(1, getWarnings().size());
}
@Test
public void testInvalidReactionId() throws Exception {
Model model = createEmptyModel();
Protein p1 = createProtein();
Protein p2 = createProtein();
Reaction p = createReaction(p1, p2);
p.setIdReaction("a-b");
model.addElement(p1);
model.addElement(p2);
model.addReaction(p);
String xml = exporter.toXml(model);
assertNotNull(xml);
assertEquals(1, getWarnings().size());
}
@Test
public void testInvalidSpeciesIdInReaction() throws Exception {
Model model = createEmptyModel();
Protein p1 = createProtein();
Protein p2 = createProtein();
Reaction p = createReaction(p1, p2);
p1.setElementId("a-b");
model.addElement(p1);
model.addElement(p2);
model.addReaction(p);
String xml = exporter.toXml(model);
assertNotNull(xml);
assertEquals(1, getWarnings().size());
}
@Test
public void testInvalidCompartmentId() throws Exception {
Model model = createEmptyModel();
Compartment p = createCompartment();
p.setElementId("a-b");
model.addElement(p);
String xml = exporter.toXml(model);
assertNotNull(xml);
assertEquals(1, getWarnings().size());
}
@Test
public void testInvalidModelId() throws Exception {
Model model = createEmptyModel();
model.setIdModel("a-b");
String xml = exporter.toXml(model);
assertNotNull(xml);
assertEquals(1, getWarnings().size());
}
}
......@@ -18,7 +18,8 @@ import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.junit.*;
import org.w3c.dom.Document;
......@@ -39,14 +40,14 @@ import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.GenericProtein;
public class SbmlTestFunctions {
private Logger logger = LogManager.getLogger();
private static int identifierCounter = 0;
@Rule
public UnitTestFailedWatcher unitTestFailedWatcher = new UnitTestFailedWatcher();
SbmlParser parser = new SbmlParser();
SbmlExporter exporter;
@SuppressWarnings("unused")
private Logger logger = Logger.getLogger(SbmlTestFunctions.class);
private MinervaLoggerAppender appender;
public SbmlTestFunctions() {
......
......@@ -163,10 +163,13 @@ AddOverlayDialog.prototype.processFile = function (file) {
descriptionInput.value = overlay.getDescription();
}
var typeSelect = $("[name='overlay-type']", self.getElement())[0];
$(typeSelect).attr("disabled", false);
if (overlay.getType() !== undefined) {
var typeSelect = $("[name='overlay-type']", self.getElement())[0];
if ($("option[value='" + overlay.getType() + "']", typeSelect).length === 0) {
GuiConnector.warn("Invalid type: " + overlay.getType());
} else {
$(typeSelect).attr("disabled", true);
}
self.setType(overlay.getType());
} else {
......
......@@ -8,7 +8,7 @@ require('datatables.net-rowreorder')();
var AddOverlayDialog = require('../AddOverlayDialog');
var Panel = require('../Panel');
var PanelControlElementType = require('../PanelControlElementType');
var ValidationError = require('../../ValidationError');
var ValidationError = require('../../ValidationError');
var GuiConnector = require('../../GuiConnector');
// noinspection JSUnusedLocalSymbols
......@@ -457,6 +457,7 @@ OverlayPanel.prototype.refresh = function (showDefault) {
var overlayTypes = [];
var selectedOverlay = [];
var overlaysFromServer = [];
return self.getServerConnector().getOverlayTypes().then(function (types) {
overlayTypes = types;
......@@ -469,10 +470,28 @@ OverlayPanel.prototype.refresh = function (showDefault) {
selectedOverlay[visibleDataOverlays[j].getId()] = true;
}
return self.getServerConnector().getOverlays();
}).then(function (overlaysFromServer) {
return self.getServerConnector().getOverlays({creator: user.getLogin()});
}).then(function (userOverlays) {
overlaysFromServer = userOverlays;
return self.getServerConnector().getOverlays({publicOverlay: true});
}).then(function (publicOverlays) {
overlaysFromServer = overlaysFromServer.concat((publicOverlays));
self.getProject().addOrUpdateDataOverlays(overlaysFromServer);
var overlays = self.getProject().getDataOverlays();
for (var index = 0; index < overlays.length; index++) {
var remove = true;
for (var index2 = 0; index2 < overlaysFromServer.length; index2++) {
if (overlaysFromServer[index2].getId() === overlays[index].getId()) {
remove = false;
}
}
if (remove) {
self.getProject().removeDataOverlay(overlays[index]);
}
}
if (!showDefault) {
if (overlaysFromServer.length === 0) {
return Promise.reject(new ValidationError("Project doesn't have a background defined. Please re-upload map in admin panel."));
......@@ -486,7 +505,7 @@ OverlayPanel.prototype.refresh = function (showDefault) {
var generalOverlays = [];
var overlay;
var overlays = self.getProject().getDataOverlays();
overlays = self.getProject().getDataOverlays();
var customOverlays = [];
var defaultOverlay = null;
for (var i = 0; i < overlays.length; i++) {
......
......@@ -38,36 +38,43 @@ describe('OverlayPanel', function () {
describe('refresh', function () {
it('anonymous', function () {
var map = helper.createCustomMap();
map.getProject().addDataOverlay(helper.createBackgroundOverlay());
var panel;
return ServerConnector.getProject("sample").then(function (project) {
project.addDataOverlay(helper.createBackgroundOverlay());
var map = helper.createCustomMap(project);
var panel = new OverlayPanel({
element: testDiv,
customMap: map
});
return panel.init().then(function () {
panel = new OverlayPanel({
element: testDiv,
customMap: map
});
return panel.init();
}).then(function () {
return panel.refresh();
}).then(function () {
assert.ok(panel.getElement().innerHTML.indexOf("testLayout") >= 0);
assert.ok(panel.getElement().innerHTML.indexOf("testLayout") < 0);
assert.ok(panel.getElement().innerHTML.indexOf("YOU ARE NOT LOGGED") >= 0);
return panel.destroy();
});
});
it('admin', function () {
helper.loginAsAdmin();
var map = helper.createCustomMap();
map.getProject().addDataOverlay(helper.createBackgroundOverlay());
var panel;
return ServerConnector.getProject("sample").then(function (project) {
project.addDataOverlay(helper.createBackgroundOverlay());
var map = helper.createCustomMap(project);
var panel = new OverlayPanel({
element: testDiv,
customMap: map
});
panel = new OverlayPanel({
element: testDiv,
customMap: map
});
return panel.init().then(function () {
return panel.init();
}).then(function () {
return panel.refresh();
}).then(function () {
assert.ok(panel.getElement().innerHTML.indexOf("testLayout") >= 0);
assert.ok(panel.getElement().innerHTML.indexOf("testLayout") < 0);
assert.ok(panel.getElement().innerHTML.indexOf("YOU ARE NOT LOGGED") < 0);
return panel.destroy();
});
});
......@@ -119,16 +126,17 @@ describe('OverlayPanel', function () {
});
});
it('download', function () {
var map = helper.createCustomMap();