Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
minerva
core
Commits
8c8bd890
Commit
8c8bd890
authored
Apr 04, 2019
by
Piotr Gawron
Browse files
Merge branch 'devel_12.2.x' into merge
parents
1d81650b
2f55dc97
Pipeline
#9654
passed with stage
in 11 minutes and 50 seconds
Changes
17
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CHANGELOG
View file @
8c8bd890
...
...
@@ -38,6 +38,21 @@ minerva (12.3.0~alpha.0) unstable; urgency=low
* Bug fix: "Unknown Catalysis" and "Unknown Inhibition" reaction end is
slightly separated from target phenotype (#664)
minerva (12.2.2) stable; urgency=medium
* Bug fix: downloading overlays didn'
t
work
from
admin
panel
when
project
with
different
id
than
default
map
was
accessed
*
Bug
fix
:
change
of
owner
of
the
data
overlay
in
admin
panel
incorrectly
ordered
overlays
(#
777
)
*
Bug
fix
:
chemical
search
didn
't use updated disease identifier, original
disease id from project upload was used instead (#779)
* Bug fix: user login with special characters (like '
@
') could cause
problems in admin panel (#780)
* Bug fix: removing project without full control in the system (but with
enough privileges to remove project) caused "Not enough privileges" error
(#778)
* Bug fix: export of custom properties (like synonyms) are properly encoded
in CellDesigner xml (#785)
minerva (12.2.1) stable; urgency=medium
* Bug fix: export of reaction colorsi in SBML is properly encoded (COPASI can
read colors properly) (#744)
...
...
annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/EntrezAnnotator.java
View file @
8c8bd890
...
...
@@ -9,6 +9,7 @@ import java.util.HashSet;
import
java.util.List
;
import
java.util.Set
;
import
org.apache.commons.text.StringEscapeUtils
;
import
org.apache.log4j.Logger
;
import
org.springframework.stereotype.Service
;
import
org.w3c.dom.Node
;
...
...
@@ -263,7 +264,7 @@ public class EntrezAnnotator extends ElementAnnotator implements IExternalServic
Node
node
=
list
.
item
(
i
);
if
(
node
.
getNodeType
()
==
Node
.
ELEMENT_NODE
)
{
if
(
node
.
getNodeName
().
equals
(
"Gene-ref_syn_E"
))
{
synonyms
.
add
(
node
.
getTextContent
());
synonyms
.
add
(
StringEscapeUtils
.
unescapeHtml4
(
node
.
getTextContent
())
)
;
}
}
}
...
...
annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/EntrezAnnotatorTest.java
View file @
8c8bd890
...
...
@@ -271,6 +271,23 @@ public class EntrezAnnotatorTest extends AnnotationTestFunctions {
}
}
@Test
public
void
testAnnotateElementWithEncodedSynonyms
()
throws
Exception
{
try
{
Species
proteinAlias
=
new
GenericProtein
(
"id"
);
proteinAlias
.
addMiriamData
(
new
MiriamData
(
MiriamType
.
ENTREZ
,
"834106"
));
entrezAnnotator
.
annotateElement
(
proteinAlias
);
for
(
String
synonym
:
proteinAlias
.
getSynonyms
())
{
assertFalse
(
"Invalid character found in synonym: "
+
synonym
,
synonym
.
contains
(
"&"
));
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
}
@Test
public
void
testParseEntrezResponse
()
throws
Exception
{
WebPageDownloader
downloader
=
entrezAnnotator
.
getWebPageDownloader
();
...
...
converter-CellDesigner/src/main/java/lcsb/mapviewer/converter/model/celldesigner/annotation/RestAnnotationParser.java
View file @
8c8bd890
...
...
@@ -160,8 +160,9 @@ public class RestAnnotationParser {
return
""
;
}
}
else
if
(
value
instanceof
String
)
{
if
(!((
String
)
value
).
trim
().
isEmpty
()
||
forceFullInfo
)
{
return
type
.
getCommonName
()
+
": "
+
value
+
"\n"
;
String
string
=(
String
)
value
;
if
(!(
string
.
trim
().
isEmpty
())
||
forceFullInfo
)
{
return
type
.
getCommonName
()
+
": "
+
XmlParser
.
escapeXml
(
string
)
+
"\n"
;
}
else
{
return
""
;
}
...
...
@@ -178,7 +179,7 @@ public class RestAnnotationParser {
if
(
object
instanceof
MiriamData
)
{
result
+=
((
MiriamData
)
object
).
getResource
();
}
else
{
result
+=
object
;
result
+=
XmlParser
.
escapeXml
(
object
.
toString
())
;
}
}
...
...
converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/CellDesignerXmlParserTest.java
View file @
8c8bd890
...
...
@@ -59,6 +59,8 @@ import lcsb.mapviewer.model.map.species.Species;
public
class
CellDesignerXmlParserTest
extends
CellDesignerTestFunctions
{
Logger
logger
=
Logger
.
getLogger
(
CellDesignerXmlParserTest
.
class
);
ModelComparator
modelComparator
=
new
ModelComparator
();
@Before
public
void
setUp
()
throws
Exception
{
}
...
...
@@ -447,8 +449,6 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
InputStream
is
=
new
ByteArrayInputStream
(
xmlString
.
getBytes
());
Model
model2
=
parser
.
createModel
(
new
ConverterParams
().
inputStream
(
is
).
sizeAutoAdjust
(
false
));
ModelComparator
modelComparator
=
new
ModelComparator
();
assertEquals
(
0
,
modelComparator
.
compare
(
model
,
model2
));
}
catch
(
Exception
e
)
{
...
...
@@ -1037,6 +1037,34 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
}
}
@Test
public
void
testSpeciesWithSpecialSynonym
()
throws
Exception
{
try
{
Model
model
=
new
ModelFullIndexed
(
null
);
model
.
setIdModel
(
"as"
);
model
.
setWidth
(
10
);
model
.
setHeight
(
10
);
Species
protein
=
new
GenericProtein
(
"id1"
);
protein
.
setWidth
(
10
);
protein
.
setHeight
(
10
);
protein
.
setName
(
"ROS"
);
protein
.
addSynonym
(
"&"
);
model
.
addElement
(
protein
);
CellDesignerXmlParser
parser
=
new
CellDesignerXmlParser
();
String
xmlString
=
parser
.
model2String
(
model
);
InputStream
is
=
new
ByteArrayInputStream
(
xmlString
.
getBytes
(
"UTF-8"
));
Model
model2
=
parser
.
createModel
(
new
ConverterParams
().
inputStream
(
is
));
assertEquals
(
0
,
modelComparator
.
compare
(
model
,
model2
));
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
}
@Test
public
void
testReactionCoordsEqual
()
throws
Exception
{
try
{
...
...
frontend-js/src/main/js/gui/admin/EditProjectDialog.js
View file @
8c8bd890
...
...
@@ -436,7 +436,8 @@ EditProjectDialog.prototype._createOverlayTable = function () {
$
(
overlaysTable
).
on
(
"
click
"
,
"
[name='downloadSource']
"
,
function
()
{
var
button
=
this
;
return
self
.
getServerConnector
().
getOverlaySourceDownloadUrl
({
overlayId
:
$
(
button
).
attr
(
"
data
"
)
overlayId
:
$
(
button
).
attr
(
"
data
"
),
projectId
:
self
.
getProject
().
getProjectId
()
}).
then
(
function
(
url
)
{
return
self
.
downloadFile
(
url
);
}).
then
(
null
,
GuiConnector
.
alert
);
...
...
@@ -990,8 +991,32 @@ EditProjectDialog.prototype.saveOverlay = function (overlayId) {
overlay
.
setDescription
(
$
(
"
[name='description-
"
+
overlayId
+
"
']
"
,
self
.
getElement
())[
0
].
value
);
overlay
.
setPublicOverlay
(
$
(
"
[name='publicOverlay-
"
+
overlayId
+
"
']
"
,
self
.
getElement
())[
0
].
checked
);
overlay
.
setDefaultOverlay
(
$
(
"
[name='defaultOverlay-
"
+
overlayId
+
"
']
"
,
self
.
getElement
())[
0
].
checked
);
overlay
.
setCreator
(
$
(
"
[name='creator-
"
+
overlayId
+
"
']
"
,
self
.
getElement
())[
0
].
value
);
var
creator
=
$
(
"
[name='creator-
"
+
overlayId
+
"
']
"
,
self
.
getElement
())[
0
].
value
;
if
(
creator
===
""
)
{
creator
=
undefined
;
}
if
(
overlay
.
getCreator
()
!==
creator
)
{
//put it on the bottom of ordered list of data overlays for given user
var
order
=
1
;
for
(
var
key
in
self
.
_overlayById
)
{
if
(
self
.
_overlayById
.
hasOwnProperty
(
key
))
{
var
existingOverlay
=
self
.
_overlayById
[
key
];
if
(
existingOverlay
.
getCreator
()
===
creator
)
{
if
(
existingOverlay
.
getId
()
!==
overlayId
)
{
order
=
Math
.
max
(
order
,
self
.
_overlayById
[
key
].
getOrder
()
+
1
);
}
else
{
order
=
Math
.
max
(
order
,
self
.
_overlayById
[
key
].
getOrder
());
}
}
}
}
if
(
creator
===
undefined
)
{
creator
=
""
;
}
overlay
.
setCreator
(
creator
);
overlay
.
setOrder
(
order
);
}
return
self
.
getServerConnector
().
updateOverlay
(
overlay
);
};
...
...
frontend-js/src/main/js/gui/admin/UsersAdminPanel.js
View file @
8c8bd890
...
...
@@ -299,7 +299,7 @@ UsersAdminPanel.prototype.addUpdateListener = function (user, dataTableRow) {
}
}
var
listener
=
function
()
{
var
login
=
user
.
getLogin
().
replace
(
"
.
"
,
"
\\
.
"
);
var
login
=
$
.
escapeSelector
(
user
.
getLogin
()
);
self
.
userToTableRow
(
user
,
dataTableRow
);
var
row
=
$
(
$
(
"
[name='usersTable']
"
,
self
.
getElement
())[
0
]).
DataTable
().
row
(
"
#
"
+
login
);
if
(
row
.
length
>
0
)
{
...
...
frontend-js/src/test/js/gui/admin/UserAdminPanel-test.js
View file @
8c8bd890
...
...
@@ -102,6 +102,18 @@ describe('UsersAdminPanel', function () {
});
});
it
(
'
onUpdateUserListener
'
,
function
()
{
helper
.
loginAsAdmin
();
var
usersTab
=
createUserAdminPanel
();
return
usersTab
.
init
().
then
(
function
()
{
var
user
=
helper
.
createUser
();
user
.
setLogin
(
"
x@y.lu
"
);
var
data
=
[];
usersTab
.
addUpdateListener
(
user
,
data
);
user
.
callListeners
(
"
onreload
"
);
assert
.
ok
(
data
.
indexOf
(
"
x@y.lu
"
)
>=
0
);
return
usersTab
.
destroy
();
});
});
})
;
});
rest-api/src/main/java/lcsb/mapviewer/api/projects/chemicals/ChemicalRestImpl.java
View file @
8c8bd890
...
...
@@ -58,7 +58,7 @@ public class ChemicalRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
if
(
project
.
getDisease
()
==
null
)
{
throw
new
QueryException
(
"Project doesn't have disease associated to it"
);
}
...
...
@@ -175,7 +175,7 @@ public class ChemicalRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
if
(
project
.
getDisease
()
==
null
)
{
throw
new
QueryException
(
"Project doesn't have disease associated to it"
);
...
...
rest-api/src/main/java/lcsb/mapviewer/api/projects/drugs/DrugRestImpl.java
View file @
8c8bd890
...
...
@@ -53,7 +53,7 @@ public class DrugRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
Set
<
String
>
columnSet
=
createDrugColumnSet
(
columns
);
...
...
@@ -159,7 +159,7 @@ public class DrugRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
List
<
Model
>
models
=
getModels
(
projectId
,
"*"
,
token
);
...
...
rest-api/src/main/java/lcsb/mapviewer/api/projects/mirnas/MiRnaRestImpl.java
View file @
8c8bd890
...
...
@@ -53,7 +53,7 @@ public class MiRnaRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
Set
<
String
>
columnSet
=
createMiRnaColumnSet
(
columns
);
...
...
@@ -129,7 +129,7 @@ public class MiRnaRestImpl extends BaseRestImpl {
if
(
model
==
null
)
{
throw
new
QueryException
(
"Project with given id doesn't exist"
);
}
Project
project
=
model
.
getProject
(
);
Project
project
=
getProjectService
().
getProjectByProjectId
(
projectId
,
token
);
List
<
Model
>
models
=
getModels
(
projectId
,
"*"
,
token
);
...
...
rest-api/src/test/java/lcsb/mapviewer/api/RestTestFunctions.java
View file @
8c8bd890
package
lcsb.mapviewer.api
;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
ArgumentMatchers
.
anyString
;
import
java.io.BufferedReader
;
import
java.io.File
;
import
java.io.FileInputStream
;
...
...
@@ -33,6 +36,7 @@ import org.apache.log4j.Logger;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.runner.RunWith
;
import
org.mockito.Mockito
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.test.annotation.Rollback
;
...
...
@@ -47,6 +51,7 @@ import org.xml.sax.InputSource;
import
org.xml.sax.SAXException
;
import
lcsb.mapviewer.common.Configuration
;
import
lcsb.mapviewer.common.exception.InvalidStateException
;
import
lcsb.mapviewer.common.exception.InvalidXmlSchemaException
;
import
lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser
;
import
lcsb.mapviewer.converter.zip.ModelZipEntryFile
;
...
...
@@ -56,6 +61,8 @@ import lcsb.mapviewer.model.map.model.Model;
import
lcsb.mapviewer.model.map.model.ModelSubmodelConnection
;
import
lcsb.mapviewer.model.map.model.SubmodelType
;
import
lcsb.mapviewer.persist.DbUtils
;
import
lcsb.mapviewer.services.interfaces.IModelService
;
import
lcsb.mapviewer.services.interfaces.IProjectService
;
import
lcsb.mapviewer.services.interfaces.IUserService
;
@Transactional
...
...
@@ -309,4 +316,21 @@ public abstract class RestTestFunctions {
return
tmp
.
toString
();
}
protected
IProjectService
createProjectMockServiceForModel
(
Model
model
)
{
try
{
IProjectService
mockModelService
=
Mockito
.
mock
(
IProjectService
.
class
);
Mockito
.
when
(
mockModelService
.
getProjectByProjectId
(
anyString
(),
anyString
())).
thenReturn
(
model
.
getProject
());
return
mockModelService
;
}
catch
(
lcsb
.
mapviewer
.
services
.
SecurityException
e
)
{
throw
new
InvalidStateException
(
e
);
}
}
protected
IModelService
createModelMockServiceForModel
(
Model
model
)
throws
lcsb
.
mapviewer
.
services
.
SecurityException
{
IModelService
mockModelService
=
Mockito
.
mock
(
IModelService
.
class
);
Mockito
.
when
(
mockModelService
.
getLastModelByProjectId
(
anyString
(),
any
())).
thenReturn
(
model
);
return
mockModelService
;
}
}
rest-api/src/test/java/lcsb/mapviewer/api/projects/ProjectRestImplTest.java
View file @
8c8bd890
...
...
@@ -33,6 +33,8 @@ import lcsb.mapviewer.converter.zip.ZipEntryFile;
import
lcsb.mapviewer.model.Project
;
import
lcsb.mapviewer.model.map.MiriamType
;
import
lcsb.mapviewer.model.map.model.Model
;
import
lcsb.mapviewer.model.user.PrivilegeType
;
import
lcsb.mapviewer.model.user.User
;
import
lcsb.mapviewer.persist.dao.ProjectDao
;
import
lcsb.mapviewer.services.interfaces.IModelService
;
import
lcsb.mapviewer.services.interfaces.IProjectService
;
...
...
@@ -81,6 +83,24 @@ public class ProjectRestImplTest extends RestTestFunctions {
}
}
@Test
public
void
testRemoveProject
()
throws
Exception
{
try
{
String
projectId
=
"test"
;
Project
project
=
new
Project
();
project
.
setProjectId
(
projectId
);
projectDao
.
add
(
project
);
User
user
=
userService
.
getUserByToken
(
token
);
userService
.
setUserPrivilege
(
user
,
PrivilegeType
.
VIEW_PROJECT
,
1
,
project
.
getId
(),
adminToken
);
userService
.
setUserPrivilege
(
user
,
PrivilegeType
.
PROJECT_MANAGEMENT
,
1
);
// userService.setUserPrivilege(user, PrivilegeType.VIEW_PROJECT, 1);
_projectRestImpl
.
removeProject
(
token
,
projectId
,
null
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
}
@Test
(
expected
=
ObjectNotFoundException
.
class
)
public
void
testGetInvalidMetaData
()
throws
Exception
{
ProjectRestImpl
projectRest
=
createMockProjectRest
(
null
);
...
...
rest-api/src/test/java/lcsb/mapviewer/api/projects/drugs/DrugRestImplTest.java
View file @
8c8bd890
package
lcsb.mapviewer.api.projects.drugs
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
mockito
.
Matchers
.
any
;
import
static
org
.
mockito
.
Matchers
.
anyString
;
import
static
org
.
mockito
.
Argument
Matchers
.
any
;
import
static
org
.
mockito
.
Argument
Matchers
.
anyString
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -16,8 +16,11 @@ import org.mockito.Mockito;
import
org.springframework.beans.factory.annotation.Autowired
;
import
lcsb.mapviewer.api.RestTestFunctions
;
import
lcsb.mapviewer.common.exception.InvalidStateException
;
import
lcsb.mapviewer.model.map.model.Model
;
import
lcsb.mapviewer.services.SecurityException
;
import
lcsb.mapviewer.services.interfaces.IModelService
;
import
lcsb.mapviewer.services.interfaces.IProjectService
;
public
class
DrugRestImplTest
extends
RestTestFunctions
{
Logger
logger
=
Logger
.
getLogger
(
DrugRestImplTest
.
class
);
...
...
@@ -53,7 +56,7 @@ public class DrugRestImplTest extends RestTestFunctions {
throw
e
;
}
}
@Test
public
void
testTargetWithEmptyMechanism
()
throws
Exception
{
try
{
...
...
@@ -69,9 +72,8 @@ public class DrugRestImplTest extends RestTestFunctions {
private
DrugRestImpl
createMockProjectRest
(
String
string
)
throws
Exception
{
Model
model
=
super
.
getModelForFile
(
string
,
true
);
IModelService
mockModelService
=
Mockito
.
mock
(
IModelService
.
class
);
Mockito
.
when
(
mockModelService
.
getLastModelByProjectId
(
anyString
(),
any
())).
thenReturn
(
model
);
_drugRestImpl
.
setModelService
(
mockModelService
);
_drugRestImpl
.
setModelService
(
createModelMockServiceForModel
(
model
));
_drugRestImpl
.
setProjectService
(
createProjectMockServiceForModel
(
model
));
return
_drugRestImpl
;
}
...
...
rest-api/src/test/java/lcsb/mapviewer/api/projects/mirnas/MiRnaRestImplTest.java
View file @
8c8bd890
package
lcsb.mapviewer.api.projects.mirnas
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
mockito
.
Matchers
.
any
;
import
static
org
.
mockito
.
Matchers
.
anyString
;
import
java.util.HashSet
;
import
java.util.List
;
...
...
@@ -14,12 +12,10 @@ import org.junit.After;
import
org.junit.AfterClass
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.mockito.Mockito
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
lcsb.mapviewer.api.RestTestFunctions
;
import
lcsb.mapviewer.model.map.model.Model
;
import
lcsb.mapviewer.services.interfaces.IModelService
;
public
class
MiRnaRestImplTest
extends
RestTestFunctions
{
Logger
logger
=
Logger
.
getLogger
(
MiRnaRestImplTest
.
class
);
...
...
@@ -58,9 +54,8 @@ public class MiRnaRestImplTest extends RestTestFunctions {
private
MiRnaRestImpl
createMockProjectRest
(
String
string
)
throws
Exception
{
Model
model
=
super
.
getModelForFile
(
string
,
true
);
IModelService
mockModelService
=
Mockito
.
mock
(
IModelService
.
class
);
Mockito
.
when
(
mockModelService
.
getLastModelByProjectId
(
anyString
(),
any
())).
thenReturn
(
model
);
_miRnaRestImpl
.
setModelService
(
mockModelService
);
_miRnaRestImpl
.
setModelService
(
createModelMockServiceForModel
(
model
));
_miRnaRestImpl
.
setProjectService
(
createProjectMockServiceForModel
(
model
));
return
_miRnaRestImpl
;
}
...
...
rest-api/src/test/resources/log4j.properties
View file @
8c8bd890
...
...
@@ -22,4 +22,4 @@ log4j.logger.lcsb.mapviewer.annotation.cache=info
log4j.logger.lcsb.mapviewer.persist
=
info
#There are plenty of useless warnings in jsbml library
log4j.logger.org.sbml.jsbml
=
error
log4j.logger.org.sbml.jsbml
=
error
\ No newline at end of file
service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java
View file @
8c8bd890
...
...
@@ -298,7 +298,6 @@ public class ProjectService implements IProjectService {
homeDir
=
null
;
}
}
removePrivilegesForProject
(
p
);
updateProjectStatus
(
p
,
ProjectStatus
.
REMOVING
,
0
,
new
CreateProjectParams
());
Thread
computations
=
new
Thread
(
new
Runnable
()
{
...
...
@@ -350,6 +349,7 @@ public class ProjectService implements IProjectService {
}
}
}
removePrivilegesForProject
(
project
);
projectDao
.
delete
(
project
);
if
(
async
)
{
projectDao
.
commit
();
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment