Commit 648100e2 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch 'devel_12.0.x'

parents 53750fc7 f858e008
...@@ -11,6 +11,18 @@ minerva (12.1.0~alpha.0) experimental; urgency=medium ...@@ -11,6 +11,18 @@ minerva (12.1.0~alpha.0) experimental; urgency=medium
-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 25 Jul 2018 10:00:00 +0200 -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 25 Jul 2018 10:00:00 +0200
minerva (12.0.2) stable; urgency=medium
* Bug fix: data overlay by annotation type fixed
* Bug fix: [plugin] getting reactions with set of id larger than 100 elements
works properly
* Bug fix: name of commented protein was remembered for the new comment
* Bug fix: setting zoom level wasn't ranged and validated
* Bug fix: when uploading data overlay there is a warning regarding mixed new
line characters when necessary
* Performance: tomcat inside docker image by default can use 3G of memory
-- Piotr Gawron <piotr.gawron@uni.lu> Tue, 31 Jul 2018 13:00:00 +0200
minerva (12.0.1) stable; urgency=medium minerva (12.0.1) stable; urgency=medium
* Bug fix: setting default zoom level on submap * Bug fix: setting default zoom level on submap
* Bug fix: opened submap had different background then current selection * Bug fix: opened submap had different background then current selection
......
...@@ -45,6 +45,7 @@ ADD policy-rc.d /tmp/policy-rc.d ...@@ -45,6 +45,7 @@ ADD policy-rc.d /tmp/policy-rc.d
RUN apt-get update RUN apt-get update
RUN sed -i "/JAVA_OPTS=\"-Djava.awt.headless=true -Xmx128m -XX:+UseConcMarkSweepGC\"/c\JAVA_OPTS=\"-Djava.awt.headless=true -Xmx3096m -XX:+UseConcMarkSweepGC\"" /etc/default/tomcat7
#when we install minerva allow to start services, so everything can be installed properly #when we install minerva allow to start services, so everything can be installed properly
#but we need to disable tomcat7 because tomcat7 requires SYS_PTRACE and we cannot start it: #but we need to disable tomcat7 because tomcat7 requires SYS_PTRACE and we cannot start it:
......
...@@ -27,7 +27,6 @@ import lcsb.mapviewer.model.map.MiriamType; ...@@ -27,7 +27,6 @@ import lcsb.mapviewer.model.map.MiriamType;
import lcsb.mapviewer.model.map.species.Chemical; import lcsb.mapviewer.model.map.species.Chemical;
import lcsb.mapviewer.modelutils.map.ElementUtils; import lcsb.mapviewer.modelutils.map.ElementUtils;
import uk.ac.ebi.chebi.webapps.chebiWS.client.ChebiWebServiceClient; import uk.ac.ebi.chebi.webapps.chebiWS.client.ChebiWebServiceClient;
import uk.ac.ebi.chebi.webapps.chebiWS.model.ChebiWebServiceFault_Exception;
import uk.ac.ebi.chebi.webapps.chebiWS.model.DataItem; import uk.ac.ebi.chebi.webapps.chebiWS.model.DataItem;
import uk.ac.ebi.chebi.webapps.chebiWS.model.Entity; import uk.ac.ebi.chebi.webapps.chebiWS.model.Entity;
import uk.ac.ebi.chebi.webapps.chebiWS.model.LiteEntity; import uk.ac.ebi.chebi.webapps.chebiWS.model.LiteEntity;
...@@ -205,7 +204,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -205,7 +204,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
} }
} }
} }
} catch (ChebiWebServiceFault_Exception e) { } catch (Exception e) {
throw new ChebiSearchException("Problem with chebi connection", e); throw new ChebiSearchException("Problem with chebi connection", e);
} }
return null; return null;
...@@ -290,7 +289,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -290,7 +289,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
String value = miriamListToStringList(result); String value = miriamListToStringList(result);
setCacheValue(query, value); setCacheValue(query, value);
} catch (ChebiWebServiceFault_Exception e) { } catch (Exception e) {
throw new ChebiSearchException("Problem with chebi", e); throw new ChebiSearchException("Problem with chebi", e);
} }
return result; return result;
...@@ -300,7 +299,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -300,7 +299,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
* Serialize list of chebi identifiers. * Serialize list of chebi identifiers.
* *
* @param list * @param list
* list of chebi identfiers * list of chebi identifiers
* @return string with identifiers * @return string with identifiers
*/ */
private String miriamListToStringList(List<MiriamData> list) { private String miriamListToStringList(List<MiriamData> list) {
...@@ -357,7 +356,6 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -357,7 +356,6 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
} }
try { try {
ChebiWebServiceClient client = getClient(); ChebiWebServiceClient client = getClient();
LiteEntityList entities = client.getLiteEntity(id, SearchCategory.CHEBI_ID, MAX_SEARCH_RESULTS_FROM_CHEBI_API, LiteEntityList entities = client.getLiteEntity(id, SearchCategory.CHEBI_ID, MAX_SEARCH_RESULTS_FROM_CHEBI_API,
StarsCategory.ALL); StarsCategory.ALL);
List<LiteEntity> resultList = entities.getListElement(); List<LiteEntity> resultList = entities.getListElement();
...@@ -374,7 +372,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -374,7 +372,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
setCacheValue("id: " + id, chebiSerializer.objectToString(result)); setCacheValue("id: " + id, chebiSerializer.objectToString(result));
} }
return result; return result;
} catch (ChebiWebServiceFault_Exception e) { } catch (Exception e) {
throw new ChebiSearchException("Problem with chebi", e); throw new ChebiSearchException("Problem with chebi", e);
} }
} }
...@@ -387,7 +385,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService ...@@ -387,7 +385,7 @@ public class ChebiAnnotator extends ElementAnnotator implements IExternalService
* identifier): "CHEBI:XXXXX" or "XXXXX" * identifier): "CHEBI:XXXXX" or "XXXXX"
* @return common name of chemical * @return common name of chemical
* @throws ChebiSearchException * @throws ChebiSearchException
* thrown when there is a problemw ith accessing information from * thrown when there is a problem with accessing information from
* external chebi database * external chebi database
*/ */
protected String getChebiNameForChebiId(MiriamData id) throws ChebiSearchException { protected String getChebiNameForChebiId(MiriamData id) throws ChebiSearchException {
......
...@@ -74,6 +74,7 @@ ServerConnector.init = function () { ...@@ -74,6 +74,7 @@ ServerConnector.init = function () {
self.removeListener("onDataLoadStop", listeners[i]); self.removeListener("onDataLoadStop", listeners[i]);
} }
self.MAX_NUMBER_OF_IDS_IN_GET_QUERY = 100;
}; };
ServerConnector.registerListenerType("onDataLoadStart"); ServerConnector.registerListenerType("onDataLoadStart");
ServerConnector.registerListenerType("onDataLoadStop"); ServerConnector.registerListenerType("onDataLoadStop");
...@@ -238,10 +239,19 @@ ServerConnector._sendRequest = function (params) { ...@@ -238,10 +239,19 @@ ServerConnector._sendRequest = function (params) {
* @returns {Promise} * @returns {Promise}
*/ */
ServerConnector.sendPostRequest = function (url, params) { ServerConnector.sendPostRequest = function (url, params) {
var formParams = {};
for (var key in params) {
if (params.hasOwnProperty(key)) {
formParams[key] = this.objectToRequestString(params[key]);
if (formParams[key] === undefined) {
formParams[key] = params[key];
}
}
}
return this.sendRequest({ return this.sendRequest({
method: "POST", method: "POST",
url: url, url: url,
form: params form: formParams
}); });
}; };
...@@ -289,6 +299,25 @@ ServerConnector.getServerBaseUrl = function () { ...@@ -289,6 +299,25 @@ ServerConnector.getServerBaseUrl = function () {
return this._serverBaseUrl; return this._serverBaseUrl;
}; };
/**
*
* @param {Object} object
* @returns {string}
*/
ServerConnector.objectToRequestString = function (object) {
var value;
if (object instanceof Point) {
value = this.pointToString(object);
} else if (Object.prototype.toString.call(object) === '[object Array]') {
value = this.idsToString(object);
} else if (typeof object === 'string' || object instanceof String || !isNaN(object)) {
value = object.toString();
} else {
value = undefined;
}
return value;
};
ServerConnector.createGetParams = function (params, prefix) { ServerConnector.createGetParams = function (params, prefix) {
var sorted = [], key; var sorted = [], key;
...@@ -306,18 +335,12 @@ ServerConnector.createGetParams = function (params, prefix) { ...@@ -306,18 +335,12 @@ ServerConnector.createGetParams = function (params, prefix) {
if (prefix !== undefined) { if (prefix !== undefined) {
key = prefix + "." + key; key = prefix + "." + key;
} }
if (value instanceof Point) {
value = this.pointToString(value);
} else if (Object.prototype.toString.call(value) === '[object Array]') {
value = this.idsToString(value);
} else if (typeof value === 'string' || value instanceof String || !isNaN(value)) {
} else {
result += this.createGetParams(value, key);
value = undefined;
}
if (value !== undefined && value !== "") { var serializedValue = this.objectToRequestString(value);
result += key + "=" + value + "&"; if (serializedValue === undefined) {
result += this.createGetParams(value, key);
} else if (serializedValue !== undefined && serializedValue !== "") {
result += key + "=" + serializedValue + "&";
} }
} }
return result; return result;
...@@ -1513,6 +1536,15 @@ ServerConnector.getOverlayById = function (overlayId, projectId) { ...@@ -1513,6 +1536,15 @@ ServerConnector.getOverlayById = function (overlayId, projectId) {
}); });
}; };
/**
*
* @param {number} [params.modelId]
* @param {number[]} [params.ids]
* @param {number[]} [params.participantId]
* @param {string[]} [params.columns]
* @param {string} [params.projectId]
* @returns {*}
*/
ServerConnector.getReactions = function (params) { ServerConnector.getReactions = function (params) {
var self = this; var self = this;
var queryParams = { var queryParams = {
...@@ -1531,7 +1563,7 @@ ServerConnector.getReactions = function (params) { ...@@ -1531,7 +1563,7 @@ ServerConnector.getReactions = function (params) {
}; };
return self.getProjectId(params.projectId).then(function (result) { return self.getProjectId(params.projectId).then(function (result) {
queryParams.projectId = result; queryParams.projectId = result;
if (filterParams.id.length > 100 || filterParams.participantId.length > 100) { if (filterParams.id.length > self.MAX_NUMBER_OF_IDS_IN_GET_QUERY || filterParams.participantId.length > self.MAX_NUMBER_OF_IDS_IN_GET_QUERY) {
return self.sendPostRequest(self.getReactionsUrl(queryParams), filterParams); return self.sendPostRequest(self.getReactionsUrl(queryParams), filterParams);
} else { } else {
return self.sendGetRequest(self.getReactionsUrl(queryParams, filterParams)); return self.sendGetRequest(self.getReactionsUrl(queryParams, filterParams));
...@@ -1571,7 +1603,7 @@ ServerConnector.getAliases = function (params) { ...@@ -1571,7 +1603,7 @@ ServerConnector.getAliases = function (params) {
}; };
return self.getProjectId(params.projectId).then(function (result) { return self.getProjectId(params.projectId).then(function (result) {
queryParams.projectId = result; queryParams.projectId = result;
if (filterParams.id.length > 100) { if (filterParams.id.length > self.MAX_NUMBER_OF_IDS_IN_GET_QUERY) {
return self.sendPostRequest(self.getAliasesUrl(queryParams), filterParams); return self.sendPostRequest(self.getAliasesUrl(queryParams), filterParams);
} else { } else {
return self.sendGetRequest(self.getAliasesUrl(queryParams, filterParams)); return self.sendGetRequest(self.getAliasesUrl(queryParams, filterParams));
......
...@@ -154,6 +154,10 @@ AddOverlayDialog.prototype.processFile = function (file) { ...@@ -154,6 +154,10 @@ AddOverlayDialog.prototype.processFile = function (file) {
if (overlay.getType() !== undefined) { if (overlay.getType() !== undefined) {
typeSelect.val(overlay.getType()); typeSelect.val(overlay.getType());
} }
if (overlayParser.containsMixedNewLineCharacters(evt.target.result)) {
GuiConnector.warn("Selected file contains new line characters from different operating systems " +
"(MAC/Windows/Linux). This might cause confusion when reading the file in the editor later on.")
}
resolve(self.getFileContent()); resolve(self.getFileContent());
} catch (error) { } catch (error) {
reject(error); reject(error);
......
...@@ -38,13 +38,13 @@ function createRow(elements) { ...@@ -38,13 +38,13 @@ function createRow(elements) {
return row; return row;
} }
CommentDialog.prototype.open = function (types) { CommentDialog.prototype.open = function (identifiedElements) {
var self = this; var self = this;
self.setTypes([CommentDialog.GENERAL]); self.setTypes([CommentDialog.GENERAL]);
var promises = [CommentDialog.GENERAL]; var promises = [CommentDialog.GENERAL];
for (var i = 0; i < types.length; i++) { for (var i = 0; i < identifiedElements.length; i++) {
var ie = types[i]; var ie = identifiedElements[i];
if (ie.getType() === "ALIAS") { if (ie.getType() === "ALIAS") {
promises.push(self.getMap().getSubmapById(ie.getModelId()).getModel().getAliasById(ie.getId(), true)); promises.push(self.getMap().getSubmapById(ie.getModelId()).getModel().getAliasById(ie.getId(), true));
} else if (ie.getType() === "REACTION") { } else if (ie.getType() === "REACTION") {
...@@ -182,6 +182,7 @@ CommentDialog.prototype.setTypes = function (types) { ...@@ -182,6 +182,7 @@ CommentDialog.prototype.setTypes = function (types) {
typeOptions.value = 0; typeOptions.value = 0;
this._types = types; this._types = types;
typeOptions.onchange();
}; };
CommentDialog.prototype.getTypes = function () { CommentDialog.prototype.getTypes = function () {
......
...@@ -114,7 +114,7 @@ EditProjectDialog.prototype.addTab = function (params) { ...@@ -114,7 +114,7 @@ EditProjectDialog.prototype.addTab = function (params) {
navigationObject: navLi, navigationObject: navLi,
navigationBar: params.tabMenuDiv navigationBar: params.tabMenuDiv
}); });
$(contentDiv).css("overflow","auto"); $(contentDiv).css("overflow", "auto");
if (params.content !== undefined) { if (params.content !== undefined) {
contentDiv.appendChild(params.content); contentDiv.appendChild(params.content);
...@@ -821,7 +821,12 @@ function getValueOrEmpty(value) { ...@@ -821,7 +821,12 @@ function getValueOrEmpty(value) {
} }
} }
EditProjectDialog.prototype.mapToTableRow = function (map, users) { /**
*
* @param {MapModel} map
* @returns {Array}
*/
EditProjectDialog.prototype.mapToTableRow = function (map) {
var row = []; var row = [];
var id = map.getId(); var id = map.getId();
var centerX = getValueOrEmpty(map.getDefaultCenterX()); var centerX = getValueOrEmpty(map.getDefaultCenterX());
...@@ -831,7 +836,17 @@ EditProjectDialog.prototype.mapToTableRow = function (map, users) { ...@@ -831,7 +836,17 @@ EditProjectDialog.prototype.mapToTableRow = function (map, users) {
row[1] = map.getName(); row[1] = map.getName();
row[2] = "<input name='defaultCenterX-" + id + "' value='" + centerX + "'/>"; row[2] = "<input name='defaultCenterX-" + id + "' value='" + centerX + "'/>";
row[3] = "<input name='defaultCenterY-" + id + "' value='" + centerY + "'/>"; row[3] = "<input name='defaultCenterY-" + id + "' value='" + centerY + "'/>";
row[4] = "<input name='defaultZoomLevel-" + id + "' value='" + zoomLevel + "'/>"; var zoomLevelSelect = "<select name='defaultZoomLevel-" + id + "' value='" + zoomLevel + "'>" +
"<option value=''>---</option>";
for (var i = map.getMinZoom(); i <= map.getMaxZoom(); i++) {
var selected = "";
if (i === zoomLevel) {
selected = " selected ";
}
zoomLevelSelect += "<option value='" + i + "' " + selected + ">" + (i - map.getMinZoom()) + "</option>";
}
zoomLevelSelect += "</select>";
row[4] = zoomLevelSelect;
row[5] = "<button name='saveMap' data='" + id + "'><i class=\"fa fa-save\" style=\"font-size:17px\"></i></button>"; row[5] = "<button name='saveMap' data='" + id + "'><i class=\"fa fa-save\" style=\"font-size:17px\"></i></button>";
return row; return row;
...@@ -921,8 +936,10 @@ EditProjectDialog.prototype.saveOverlay = function (overlayId) { ...@@ -921,8 +936,10 @@ EditProjectDialog.prototype.saveOverlay = function (overlayId) {
EditProjectDialog.prototype.saveMap = function (mapId) { EditProjectDialog.prototype.saveMap = function (mapId) {
var self = this; var self = this;
var map = self._mapsById[mapId]; var map = self._mapsById[mapId];
map.setDefaultCenterX($("[name='defaultCenterX-" + mapId + "']", self.getElement())[0].value); var centerX = parseInt($("[name='defaultCenterX-" + mapId + "']", self.getElement())[0].value);
map.setDefaultCenterY($("[name='defaultCenterY-" + mapId + "']", self.getElement())[0].value); var centerY = parseInt($("[name='defaultCenterY-" + mapId + "']", self.getElement())[0].value);
map.setDefaultCenterX(centerX);
map.setDefaultCenterY(centerY);
map.setDefaultZoomLevel($("[name='defaultZoomLevel-" + mapId + "']", self.getElement())[0].value); map.setDefaultZoomLevel($("[name='defaultZoomLevel-" + mapId + "']", self.getElement())[0].value);
return ServerConnector.updateModel({projectId: self.getProject().getProjectId(), model: map}); return ServerConnector.updateModel({projectId: self.getProject().getProjectId(), model: map});
......
...@@ -9,13 +9,24 @@ function OverlayParser() { ...@@ -9,13 +9,24 @@ function OverlayParser() {
/** /**
* *
* @param {string| Uint8Array|ArrayBuffer} content * @param {string| Uint8Array | ArrayBuffer} content
* @returns {DataOverlay} * @returns {string}
* @private
*/ */
OverlayParser.prototype.parse = function (content) { OverlayParser.prototype._extractContent = function (content) {
if (content instanceof Uint8Array || content instanceof ArrayBuffer) { if (content instanceof Uint8Array || content instanceof ArrayBuffer) {
content = new TextDecoder("UTF8").decode(content); content = new TextDecoder("UTF8").decode(content);
} }
return content;
};
/**
*
* @param {string| Uint8Array|ArrayBuffer} content
* @returns {DataOverlay}
*/
OverlayParser.prototype.parse = function (content) {
content = this._extractContent(content);
var data = {content: content}; var data = {content: content};
var lines = content.split("\n"); var lines = content.split("\n");
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
...@@ -42,5 +53,36 @@ OverlayParser.prototype.parse = function (content) { ...@@ -42,5 +53,36 @@ OverlayParser.prototype.parse = function (content) {
return new DataOverlay(data); return new DataOverlay(data);
}; };
/**
*
* @param {string| Uint8Array | ArrayBuffer} content
* @returns {boolean}
*/
OverlayParser.prototype.containsMixedNewLineCharacters = function (content) {
content = this._extractContent(content);
var newLineRegEx = /[\r\n]+/g;
var match = newLineRegEx.exec(content);
var newLineFormats = {};
var counter = 0;
while (match !== null && match !== undefined) {
var foundMultiplication = false;
var key = '';
//this is just a heuristic - let's assume there are at most 10 empty lines in a file
for (var i = 0; i < 10; i++) {
key += match[0];
if (newLineFormats[key]) {
foundMultiplication = true;
} else {
newLineFormats[key] = true;
}
}
if (!foundMultiplication) {
counter++;
}
match = newLineRegEx.exec(content);
}
return counter > 1;
};
module.exports = OverlayParser; module.exports = OverlayParser;
...@@ -455,8 +455,16 @@ MapModel.prototype.getDefaultZoomLevel = function () { ...@@ -455,8 +455,16 @@ MapModel.prototype.getDefaultZoomLevel = function () {
return this._defaultZoomLevel; return this._defaultZoomLevel;
}; };
/**
*
* @param {number} defaultZoomLevel
*/
MapModel.prototype.setDefaultZoomLevel = function (defaultZoomLevel) { MapModel.prototype.setDefaultZoomLevel = function (defaultZoomLevel) {
this._defaultZoomLevel = defaultZoomLevel; if (!isNaN(defaultZoomLevel)) {
this._defaultZoomLevel = defaultZoomLevel;
} else {
this._defaultZoomLevel = null;
}
}; };
/** /**
...@@ -467,8 +475,16 @@ MapModel.prototype.getDefaultCenterX = function () { ...@@ -467,8 +475,16 @@ MapModel.prototype.getDefaultCenterX = function () {
return this._defaultCenterX; return this._defaultCenterX;
}; };
/**
*
* @param {number} defaultCenterX
*/
MapModel.prototype.setDefaultCenterX = function (defaultCenterX) { MapModel.prototype.setDefaultCenterX = function (defaultCenterX) {
this._defaultCenterX = defaultCenterX; if (!isNaN(defaultCenterX)) {
this._defaultCenterX = defaultCenterX;
} else {
this._defaultCenterX = null;
}
}; };
/** /**
...@@ -480,7 +496,11 @@ MapModel.prototype.getDefaultCenterY = function () { ...@@ -480,7 +496,11 @@ MapModel.prototype.getDefaultCenterY = function () {
}; };
MapModel.prototype.setDefaultCenterY = function (defaultCenterY) { MapModel.prototype.setDefaultCenterY = function (defaultCenterY) {
this._defaultCenterY = defaultCenterY; if (!isNaN(defaultCenterY)) {
this._defaultCenterY = defaultCenterY;
} else {
this._defaultCenterY = null;
}
}; };
MapModel.prototype.getSubmodelType = function () { MapModel.prototype.getSubmodelType = function () {
......
...@@ -13,6 +13,7 @@ var LayoutAlias = require('../../main/js/map/data/LayoutAlias'); ...@@ -13,6 +13,7 @@ var LayoutAlias = require('../../main/js/map/data/LayoutAlias');
var MapModel = require('../../main/js/map/data/MapModel'); var MapModel = require('../../main/js/map/data/MapModel');
var NetworkError = require('../../main/js/NetworkError'); var NetworkError = require('../../main/js/NetworkError');
var Project = require('../../main/js/map/data/Project'); var Project = require('../../main/js/map/data/Project');
var Point = require('../../main/js/map/canvas/Point');
var Reaction = require('../../main/js/map/data/Reaction'); var Reaction = require('../../main/js/map/data/Reaction');
var ServerConnector = require('../../main/js/ServerConnector'); var ServerConnector = require('../../main/js/ServerConnector');
var SecurityError = require('../../main/js/SecurityError'); var SecurityError = require('../../main/js/SecurityError');
...@@ -110,7 +111,7 @@ describe('ServerConnector', function () { ...@@ -110,7 +111,7 @@ describe('ServerConnector', function () {
}); });
}); });
it('without ids', function () { it('without ids', function () {
return ServerConnector.getReactions([]).then(function (result) { return ServerConnector.getReactions({}).then(function (result) {
assert.equal(result.length, 28); assert.equal(result.length, 28);
var reaction = result[0]; var reaction = result[0];
assert.ok(reaction instanceof Reaction); assert.ok(reaction instanceof Reaction);
...@@ -118,6 +119,32 @@ describe('ServerConnector', function () { ...@@ -118,6 +119,32 @@ describe('ServerConnector', function () {
assert.equal(reaction.getModelId(), 15781); assert.equal(reaction.getModelId(), 15781);
}); });
}); });