From dd7c78a0b1588b5a739f49ee5805ef8cd22ae5c2 Mon Sep 17 00:00:00 2001 From: David Hoksza <david.hoksza@uni.lu> Date: Tue, 30 Jan 2018 14:34:39 +0100 Subject: [PATCH] Annotator parameters passed to annotators (annotation fails with LazyInitializationException) --- .../annotation/services/ModelAnnotator.java | 47 ++++++++++++++++--- .../services/annotators/ElementAnnotator.java | 3 +- .../services/annotators/KeggAnnotator.java | 7 ++- .../annotators/AnnotatorExceptionTest.java | 2 +- frontend-js/src/main/css/global.css | 2 +- .../model/user/UserAnnotationSchema.java | 12 ++--- ...rsParams.java => UserAnnotatorsParam.java} | 10 ++-- .../resources/applicationContext-persist.xml | 2 +- .../api/projects/ProjectRestImpl.java | 1 + .../mapviewer/api/users/UserRestImpl.java | 11 ++--- .../services/impl/ProjectService.java | 15 +++++- .../services/interfaces/IProjectService.java | 8 ++++ .../services/utils/CreateProjectParams.java | 37 +++++++++++++++ 13 files changed, 126 insertions(+), 31 deletions(-) rename model/src/main/java/lcsb/mapviewer/model/user/{UserAnnotatorsParams.java => UserAnnotatorsParam.java} (90%) diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java index d4bfb99a23..bc053783e1 100644 --- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java +++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java @@ -12,10 +12,12 @@ import java.util.Set; import javax.annotation.PostConstruct; +import org.apache.commons.logging.Log; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import lcsb.mapviewer.annotation.services.annotators.AnnotatorException; +import lcsb.mapviewer.annotation.services.annotators.AnnotatorParamDefinition; import lcsb.mapviewer.annotation.services.annotators.BrendaAnnotator; import lcsb.mapviewer.annotation.services.annotators.BiocompendiumAnnotator; import lcsb.mapviewer.annotation.services.annotators.CazyAnnotator; @@ -41,6 +43,7 @@ import lcsb.mapviewer.model.map.model.Model; import lcsb.mapviewer.model.map.reaction.Reaction; import lcsb.mapviewer.model.map.species.Element; import lcsb.mapviewer.model.map.species.Species; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; import lcsb.mapviewer.modelutils.map.ClassTreeNode; import lcsb.mapviewer.modelutils.map.ElementUtils; @@ -227,7 +230,7 @@ public class ModelAnnotator { * callback function used for updating progress of the function */ public void performAnnotations(Model model, final IProgressUpdater progressUpdater) { - performAnnotations(model, progressUpdater, null); + performAnnotations(model, progressUpdater, null, null); } /** @@ -241,7 +244,8 @@ public class ModelAnnotator { * @param progressUpdater * callback function used for updating progress of the function */ - public void performAnnotations(Model model, final IProgressUpdater progressUpdater, Map<Class<?>, List<ElementAnnotator>> annotators) { + public void performAnnotations(Model model, final IProgressUpdater progressUpdater, Map<Class<?>, + List<ElementAnnotator>> annotators, Map<Class<?>, List<UserAnnotatorsParam>> annotatorsParams) { progressUpdater.setProgress(0); List<Model> models = new ArrayList<Model>(); models.add(model); @@ -264,7 +268,7 @@ public class ModelAnnotator { progressUpdater .setProgress(ratio * IProgressUpdater.MAX_PROGRESS + (COPYING_RATIO * IProgressUpdater.MAX_PROGRESS + progress * ANNOTATING_RATIO) / size); } - }, annotators); + }, annotators, annotatorsParams); counter++; } } @@ -303,7 +307,7 @@ public class ModelAnnotator { progressUpdater.setProgress(counter / amount * IProgressUpdater.MAX_PROGRESS); } } - + /** * Annotates all elements in the model using set of {@link ElementAnnotator} * given in the param. If the set is empty then all annotators will be used. @@ -315,14 +319,37 @@ public class ModelAnnotator { * model where annotation shoud be updated * @param progressUpdater * callback function used to refresh progress of function execution - */ + */ protected void annotateModel(Model model, IProgressUpdater progressUpdater, Map<Class<?>, List<ElementAnnotator>> annotators) { + annotateModel(model, progressUpdater, annotators, null); + } + + /** + * Annotates all elements in the model using set of {@link ElementAnnotator} + * given in the param. If the set is empty then all annotators will be used. + * + * @param annotators + * this map contains lists of {@link ElementAnnotator} objects that + * should be used for a given classes + * @param annotatorsParams + * this map contains lists of {@link UserAnnotatorsParam} objects that + * should be used for a given {@link ElementAnnotator} class + * @param model + * model where annotation should be updated + * @param progressUpdater + * callback function used to refresh progress of function execution + */ + protected void annotateModel(Model model, IProgressUpdater progressUpdater, Map<Class<?>, List<ElementAnnotator>> annotators, + Map<Class<?>, List<UserAnnotatorsParam>> annotatorsParams) { ElementUtils elementUtils = new ElementUtils(); progressUpdater.setProgress(0); double counter = 0; double amount = model.getElements().size() + model.getReactions().size(); + + logger.debug("annotatorsParams"); + logger.debug(annotatorsParams); // annotate all elements for (Element element : model.getElements()) { @@ -336,7 +363,15 @@ public class ModelAnnotator { for (ElementAnnotator elementAnnotator : list) { try { - elementAnnotator.annotateElement(element); + List<UserAnnotatorsParam> params = annotatorsParams.get(elementAnnotator.getClass()); + logger.debug("params"); + logger.debug(params); + if (params != null) { + elementAnnotator.annotateElement(element, params); + } else { + elementAnnotator.annotateElement(element); + } + } catch (AnnotatorException e) { logger.warn(elementUtils.getElementTag(element) + " " + elementAnnotator.getCommonName() + " annotation problem: " + e.getMessage()); } diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/ElementAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/ElementAnnotator.java index a3fe33f3d3..b353a3f86b 100644 --- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/ElementAnnotator.java +++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/ElementAnnotator.java @@ -18,6 +18,7 @@ import lcsb.mapviewer.model.map.reaction.Reaction; import lcsb.mapviewer.model.map.species.Chemical; import lcsb.mapviewer.model.map.species.Element; import lcsb.mapviewer.model.map.species.Species; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; /** * Interface that allows to annotate {@link BioEntity elements} in the system. @@ -96,7 +97,7 @@ public abstract class ElementAnnotator extends CachableInterface { * thrown when there is a problem with annotating not related to * data */ - public void annotateElement(BioEntity element, List<Object> parameters) throws AnnotatorException { + public void annotateElement(BioEntity element, List<UserAnnotatorsParam> parameters) throws AnnotatorException { annotateElement(element); } diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java index 95f7c6be70..51654c7b6b 100644 --- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java +++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java @@ -148,8 +148,6 @@ public class KeggAnnotator extends ElementAnnotator implements IExternalService } if (mdUniprot != null) uniprotAnnotator.annotateElement(object); - - Set<String> ecs = new HashSet<String>(); for (MiriamData md : object.getMiriamData()) { if (md.getDataType().equals(MiriamType.EC)) { @@ -201,11 +199,16 @@ public class KeggAnnotator extends ElementAnnotator implements IExternalService * @return {@link MiriamType#PUBMED}s found on the page */ private Collection<MiriamData> parseKegg(String pageContent) { + + //Retrieve Pubmeds Collection<MiriamData> result = new HashSet<MiriamData>(); Matcher m = pubmedMatcher.matcher(pageContent); while (m.find()) { result.add(createMiriamData(MiriamType.PUBMED, m.group(1))); } + + //Retrieve homologous organisms based on parameterization + return result; } diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AnnotatorExceptionTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AnnotatorExceptionTest.java index a18773f47d..05162124a4 100644 --- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AnnotatorExceptionTest.java +++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AnnotatorExceptionTest.java @@ -23,7 +23,7 @@ public class AnnotatorExceptionTest { @Test public void testConstructor() { - AnnotatorParamDefinition d = new AnnotatorParamDefinition("name", "description", String.class); + AnnotatorParamDefinition d = new AnnotatorParamDefinition("name", String.class, "description"); assertEquals(d.getName(), "name"); assertEquals(d.getDecription(), "description"); assertEquals(d.getType(), String.class); diff --git a/frontend-js/src/main/css/global.css b/frontend-js/src/main/css/global.css index 4781ed6c0f..04c3b36d73 100644 --- a/frontend-js/src/main/css/global.css +++ b/frontend-js/src/main/css/global.css @@ -552,7 +552,7 @@ h1 { .minerva-annotator-param { display: table; width: 100%; - + padding-bottom: 5px; } .minerva-annotator-param-name { diff --git a/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotationSchema.java b/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotationSchema.java index e892d82d38..398f339169 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotationSchema.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotationSchema.java @@ -98,7 +98,7 @@ public class UserAnnotationSchema implements Serializable { @Cascade({ CascadeType.ALL }) @OneToMany(mappedBy = "annotationSchema") @OrderBy("id") - private List<UserAnnotatorsParams> annotatorsParams = new ArrayList<>(); + private List<UserAnnotatorsParam> annotatorsParams = new ArrayList<>(); /** * List of valid annotations for given object type. @@ -170,7 +170,7 @@ public class UserAnnotationSchema implements Serializable { /** * @return the annotatorsParams */ - public List<UserAnnotatorsParams> getAnnotatorsParams() { + public List<UserAnnotatorsParam> getAnnotatorsParams() { return annotatorsParams; } @@ -178,7 +178,7 @@ public class UserAnnotationSchema implements Serializable { * @param annotatorsParams * the annotatorsParams to set */ - public void setAnnotatorsParams(List<UserAnnotatorsParams> annotatorsParams) { + public void setAnnotatorsParams(List<UserAnnotatorsParam> annotatorsParams) { this.annotatorsParams = annotatorsParams; } @@ -226,13 +226,13 @@ public class UserAnnotationSchema implements Serializable { } /** - * Adds (or updates) {@link UserAnnotatorsParams} information + * Adds (or updates) {@link UserAnnotatorsParam} information * to {@link #annotatorsParams}. * * @param ap * object to add/update */ - public void addAnnotatorParam(UserAnnotatorsParams ap) { + public void addAnnotatorParam(UserAnnotatorsParam ap) { if (ap.getAnnotatorClassName() == null) { throw new InvalidArgumentException("Class name cannot be null"); } @@ -244,7 +244,7 @@ public class UserAnnotationSchema implements Serializable { } for (int i = 0; i < this.annotatorsParams.size(); i++) { - UserAnnotatorsParams params = this.annotatorsParams.get(i); + UserAnnotatorsParam params = this.annotatorsParams.get(i); if (params.getAnnotatorClassName().equals(ap.getAnnotatorClassName()) && params.getParamName().equals(ap.getParamName())) { this.annotatorsParams.get(i).setParamValue(ap.getParamValue()); diff --git a/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParams.java b/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParam.java similarity index 90% rename from model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParams.java rename to model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParam.java index 36822aaafc..da0802ac8e 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParams.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/UserAnnotatorsParam.java @@ -19,7 +19,7 @@ import javax.persistence.Table; */ @Entity @Table(name = "annotators_params_table") -public class UserAnnotatorsParams implements Serializable { +public class UserAnnotatorsParam implements Serializable { /** * @@ -63,7 +63,7 @@ public class UserAnnotatorsParams implements Serializable { /** * Default constructor. */ - public UserAnnotatorsParams() { + public UserAnnotatorsParam() { } @@ -75,7 +75,7 @@ public class UserAnnotatorsParams implements Serializable { * @param annotators * {@link #annotators} */ - public UserAnnotatorsParams(Class<?> annotatorClassName, String paramName, String paramValue) { + public UserAnnotatorsParam(Class<?> annotatorClassName, String paramName, String paramValue) { setAnnotatorClassName(annotatorClassName); setParamName(paramName); setParamValue(paramValue); @@ -142,7 +142,7 @@ public class UserAnnotatorsParams implements Serializable { /** * @param paramName - * the {@link UserAnnotatorsParams#paramName} to set + * the {@link UserAnnotatorsParam#paramName} to set */ public void setParamName(String paramName) { this.paramName = paramName; @@ -158,7 +158,7 @@ public class UserAnnotatorsParams implements Serializable { /** * @param parameter value - * the {@link UserAnnotatorsParams#paramValue} to set + * the {@link UserAnnotatorsParam#paramValue} to set */ public void setParamValue(String paramValue) { this.paramValue = paramValue; diff --git a/persist/src/main/resources/applicationContext-persist.xml b/persist/src/main/resources/applicationContext-persist.xml index a4c7584840..7743d71430 100644 --- a/persist/src/main/resources/applicationContext-persist.xml +++ b/persist/src/main/resources/applicationContext-persist.xml @@ -85,7 +85,7 @@ <value>lcsb.mapviewer.model.user.Configuration</value> <value>lcsb.mapviewer.model.user.ObjectPrivilege</value> <value>lcsb.mapviewer.model.user.User</value> - <value>lcsb.mapviewer.model.user.UserAnnotatorsParams</value> + <value>lcsb.mapviewer.model.user.UserAnnotatorsParam</value> <value>lcsb.mapviewer.model.user.UserAnnotationSchema</value> <value>lcsb.mapviewer.model.user.UserClassAnnotators</value> <value>lcsb.mapviewer.model.user.UserClassValidAnnotations</value> diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java index c4351cfc3f..a0b4397921 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java @@ -552,6 +552,7 @@ public class ProjectRestImpl extends BaseRestImpl { params.requiredAnnotations(projectService.createClassAnnotatorTree(user)); params.validAnnotations(projectService.createClassAnnotatorTree(user)); } + params.setAnnotatorParams(projectService.getAnnotatorsParams(user)); getProjectService().createProject(params); return getProject(projectId, token); diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java index c105de4969..4b60023f4f 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java @@ -23,7 +23,7 @@ import lcsb.mapviewer.model.user.ObjectPrivilege; import lcsb.mapviewer.model.user.PrivilegeType; import lcsb.mapviewer.model.user.User; import lcsb.mapviewer.model.user.UserAnnotationSchema; -import lcsb.mapviewer.model.user.UserAnnotatorsParams; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; import lcsb.mapviewer.model.user.UserClassAnnotators; import lcsb.mapviewer.model.user.UserClassRequiredAnnotations; import lcsb.mapviewer.model.user.UserClassValidAnnotations; @@ -144,9 +144,9 @@ public class UserRestImpl extends BaseRestImpl { * @param annotatorsParams * @return */ - private Map<String, Object> prepareAnnotatorsParams(List<UserAnnotatorsParams> annotatorsParams) { + private Map<String, Object> prepareAnnotatorsParams(List<UserAnnotatorsParam> annotatorsParams) { Map<String, Object> result = new HashMap<>(); - for (UserAnnotatorsParams param : annotatorsParams) { + for (UserAnnotatorsParam param : annotatorsParams) { String className = param.getAnnotatorClassName().getName(); Map<String, String> annotatorParams = (Map<String, String>)result.get(className); if (annotatorParams == null) { @@ -192,7 +192,7 @@ public class UserRestImpl extends BaseRestImpl { for (String name: nameValueS.keySet()) { String value = (String)nameValueS.get(name); try { - UserAnnotatorsParams param = new UserAnnotatorsParams(Class.forName(annotatorClassname), name, value); + UserAnnotatorsParam param = new UserAnnotatorsParam(Class.forName(annotatorClassname), name, value); schema.addAnnotatorParam(param); } catch (ClassNotFoundException e) { throw new QueryException("Unknown annotator class name: " + annotatorClassname); @@ -439,9 +439,6 @@ public class UserRestImpl extends BaseRestImpl { for (String key : preferencesData.keySet()) { Map<String, Object> value = (Map<String, Object>) preferencesData.get(key); - logger.debug(key); - logger.debug(value); - if (key.equals("project-upload")) { updateUploadPreferences(schema, value); } else if (key.equals("element-annotators")) { diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java b/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java index 5e6664973f..c8409f670f 100644 --- a/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java +++ b/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java @@ -77,6 +77,7 @@ import lcsb.mapviewer.model.user.ObjectPrivilege; import lcsb.mapviewer.model.user.PrivilegeType; import lcsb.mapviewer.model.user.User; import lcsb.mapviewer.model.user.UserAnnotationSchema; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; import lcsb.mapviewer.model.user.UserClassAnnotators; import lcsb.mapviewer.model.user.UserClassRequiredAnnotations; import lcsb.mapviewer.model.user.UserClassValidAnnotations; @@ -827,7 +828,7 @@ public class ProjectService implements IProjectService { public void setProgress(final double progress) { updateProjectStatus(project, ProjectStatus.ANNOTATING, progress, params); } - }, annotators); + }, annotators, params.getAnnotatorsParamsAsMap()); logger.debug("Annotations updated"); } updateProjectStatus(project, ProjectStatus.EXTENDING_MODEL, 0.0, params); @@ -1458,6 +1459,18 @@ public class ProjectService implements IProjectService { userService.updateUser(dbUser); user.setAnnotationSchema(annotationSchema); } + + @Override + public List<UserAnnotatorsParam> getAnnotatorsParams(User user) { + User dbUser = userDao.getById(user.getId()); + UserAnnotationSchema annotationSchema = dbUser.getAnnotationSchema(); + if (annotationSchema == null) { + annotationSchema = new UserAnnotationSchema(); + dbUser.setAnnotationSchema(annotationSchema); + } + + return annotationSchema.getAnnotatorsParams(); + } /** * Sends email about unsuccessfull project creation. diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java index 651d472fdc..9d504bffe3 100644 --- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java +++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java @@ -7,6 +7,7 @@ import org.primefaces.model.TreeNode; import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.user.User; import lcsb.mapviewer.model.user.UserAnnotationSchema; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; import lcsb.mapviewer.services.SecurityException; import lcsb.mapviewer.services.UserAccessException; import lcsb.mapviewer.services.utils.CreateProjectParams; @@ -189,5 +190,12 @@ public interface IProjectService { byte[] getInputDataForProject(ProjectView projectView); UserAnnotationSchema prepareUserAnnotationSchema(User user); + + /** + * Retrieves list of annotators {@link UserAnnotatorsParam parameters} + * @return + * list of annotators parameters + */ + List<UserAnnotatorsParam> getAnnotatorsParams(User user); } diff --git a/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java b/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java index c6b976f181..8a253f2742 100644 --- a/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java +++ b/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java @@ -16,10 +16,12 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.primefaces.model.TreeNode; +import lcsb.mapviewer.annotation.services.annotators.AnnotatorParamDefinition; import lcsb.mapviewer.converter.IConverter; import lcsb.mapviewer.converter.zip.ZipEntryFile; import lcsb.mapviewer.model.map.BioEntity; import lcsb.mapviewer.model.map.MiriamType; +import lcsb.mapviewer.model.user.UserAnnotatorsParam; import lcsb.mapviewer.services.overlay.AnnotatedObjectTreeRow; import lcsb.mapviewer.services.view.AuthenticationToken; @@ -160,6 +162,11 @@ public class CreateProjectParams { * obligatory for which class. */ private Map<Class<? extends BioEntity>, Set<MiriamType>> requiredAnnotations = null; + + /** + * List with annotators parameters. + */ + private List<UserAnnotatorsParam> annotatorsParams = null; /** * @param projectId @@ -544,6 +551,36 @@ public class CreateProjectParams { public void setAnnotatorsMap(Map<Class<?>, List<String>> annotatorsMap) { this.annotatorsMap = annotatorsMap; } + + /** + * @return the annotators params + */ + public List<UserAnnotatorsParam> getAnnotatorsParams() { + return annotatorsParams; + } + + public Map<Class<?>, List<UserAnnotatorsParam>> getAnnotatorsParamsAsMap(){ + + Map<Class<?>, List<UserAnnotatorsParam>> paramsMap = new HashMap<>(); + + for (UserAnnotatorsParam param: getAnnotatorsParams()){ + Class<?> annotatorClassName = param.getAnnotatorClassName(); + if (!paramsMap.containsKey(annotatorClassName)) { + paramsMap.put(annotatorClassName, new ArrayList<>()); + } + paramsMap.get(annotatorClassName).add(param); + } + + return paramsMap; + } + + /** + * @param annotatorsParams + * the annotatorsParams to set + */ + public void setAnnotatorParams(List<UserAnnotatorsParam> annotatorsParams) { + this.annotatorsParams = annotatorsParams; + } /** * Creates {@link #validAnnotations}. -- GitLab