diff --git a/.gitignore b/.gitignore
index b5f8abf7bbb1596d85af3b087183575a4cdfb403..408e05823a09530dab2c0824536641bb1b6acae7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ openbis/source/core-plugins
 **/.pydevproject
 **/.shredder
 ivy-repository
+**/.idea
diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/DefaultSessionManager.java b/authentication/source/java/ch/systemsx/cisd/authentication/DefaultSessionManager.java
index 41f9aeaaea898d05cce102206d3a6a461d1a526b..778350bcd3b519591fe40bb8235560537cb6b812 100644
--- a/authentication/source/java/ch/systemsx/cisd/authentication/DefaultSessionManager.java
+++ b/authentication/source/java/ch/systemsx/cisd/authentication/DefaultSessionManager.java
@@ -19,11 +19,15 @@ package ch.systemsx.cisd.authentication;
 import java.io.File;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.annotation.Resource;
 
@@ -32,6 +36,7 @@ import org.apache.commons.lang3.time.DateUtils;
 import org.apache.commons.lang3.time.DurationFormatUtils;
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.collection.SimpleComparator;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.InvalidSessionException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -55,6 +60,11 @@ import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
  */
 public class DefaultSessionManager<T extends BasicSession> implements ISessionManager<T>
 {
+    private enum SessionClosingReason
+    {
+        LOGOUT, SESSION_EXPIRATION, SESSIONS_LIMIT
+    }
+
     public static final File NO_LOGIN_FILE = new File("./etc/nologin.html");
 
     private static final String LOGOUT_PREFIX = "LOGOUT: ";
@@ -143,6 +153,8 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
 
     private final boolean tryEmailAsUserName;
 
+    private final Set<ISessionActionListener> listeners = new LinkedHashSet<>();
+
     public DefaultSessionManager(final ISessionFactory<T> sessionFactory,
             final ILogMessagePrefixGenerator<T> prefixGenerator,
             final IAuthenticationService authenticationService,
@@ -212,11 +224,11 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
             int maxNumberOfSessions = getMaxNumberOfSessionsFor(user);
             if (maxNumberOfSessions > 0)
             {
-                int numberOfOpenSessions = getNumberOfOpenSessionsFor(user);
-                if (numberOfOpenSessions >= maxNumberOfSessions)
+                List<FullSession<T>> openSessions = getOpenSessionsFor(user);
+                while (openSessions.size() >= maxNumberOfSessions)
                 {
-                    throw new UserFailureException(numberOfOpenSessions + " open session(s) but only "
-                            + maxNumberOfSessions + " session(s) are allowed for user " + user + ".");
+                    FullSession<T> session = openSessions.remove(0);
+                    closeSession(session.getSession(), SessionClosingReason.SESSIONS_LIMIT);
                 }
             }
 
@@ -232,18 +244,25 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
         }
     }
 
-    private int getNumberOfOpenSessionsFor(String user)
+    private List<FullSession<T>> getOpenSessionsFor(String user)
     {
-        int count = 0;
-        Collection<FullSession<T>> values = sessions.values();
-        for (FullSession<T> session : values)
+        List<FullSession<T>> userSessions = new ArrayList<>();
+        for (FullSession<T> session : sessions.values())
         {
             if (session.getSession().getUserName().equals(user))
             {
-                count++;
+                userSessions.add(session);
             }
         }
-        return count;
+        Collections.sort(userSessions, new SimpleComparator<FullSession<T>, Long>()
+            {
+                @Override
+                public Long evaluate(FullSession<T> session)
+                {
+                    return session.lastActiveTime;
+                }
+            });
+        return userSessions;
     }
 
     protected int getMaxNumberOfSessionsFor(String user)
@@ -453,6 +472,18 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
         }
     }
 
+    private void logLimitedNumberOfSessions(final T session)
+    {
+        final String prefix = prefixGenerator.createPrefix(session);
+        authenticationLog.info(prefix + ": session closed because limit of open session has been reached.");
+        if (operationLog.isInfoEnabled())
+        {
+            final String user = session.getUserName();
+            operationLog.info(LOGOUT_PREFIX + "Session '" + session.getSessionToken()
+                    + "' of user '" + user + "' has been closed because limit of open session has been reached.");
+        }
+    }
+
     @Override
     public boolean isAWellFormedSessionToken(String sessionTokenOrNull)
     {
@@ -540,7 +571,7 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
             }
             if (checkAndTouch && doSessionExpiration(session))
             {
-                closeSession(session.getSession(), false);
+                closeSession(session.getSession(), SessionClosingReason.SESSION_EXPIRATION);
             }
             if (checkAndTouch && isSessionUnavailable(session))
             {
@@ -615,7 +646,7 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
         synchronized (sessions)
         {
             final T session = getSession(sessionToken, false);
-            closeSession(session, true);
+            closeSession(session, SessionClosingReason.LOGOUT);
         }
     }
 
@@ -623,22 +654,32 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
     public void expireSession(String sessionToken) throws InvalidSessionException
     {
         final T session = getSession(sessionToken, false);
-        closeSession(session, false);
+        closeSession(session, SessionClosingReason.SESSION_EXPIRATION);
     }
 
-    private void closeSession(final T session, final boolean regularLogout)
+    private void closeSession(final T session, SessionClosingReason reason)
             throws InvalidSessionException
     {
         synchronized (sessions)
         {
             session.cleanup();
-            sessions.remove(session.getSessionToken());
-            if (regularLogout)
+            String sessionToken = session.getSessionToken();
+            sessions.remove(sessionToken);
+            switch (reason)
             {
-                logLogout(session);
-            } else
+                case LOGOUT:
+                    logLogout(session);
+                    break;
+                case SESSION_EXPIRATION:
+                    logSessionExpired(session);
+                    break;
+                case SESSIONS_LIMIT:
+                    logLimitedNumberOfSessions(session);
+                    break;
+            }
+            for (ISessionActionListener listener : listeners)
             {
-                logSessionExpired(session);
+                listener.sessionClosed(sessionToken);
             }
         }
     }
@@ -659,4 +700,11 @@ public class DefaultSessionManager<T extends BasicSession> implements ISessionMa
         }
         return p;
     }
+
+    @Override
+    public void addListener(ISessionActionListener listener)
+    {
+        listeners.add(listener);
+    }
+
 }
diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/ISessionActionListener.java b/authentication/source/java/ch/systemsx/cisd/authentication/ISessionActionListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c8803b0a94e6c5c93b56d4346bbd2dd260decdb
--- /dev/null
+++ b/authentication/source/java/ch/systemsx/cisd/authentication/ISessionActionListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.authentication;
+
+/**
+ * @author Franz-Josef Elmer
+ *
+ */
+public interface ISessionActionListener
+{
+    public void sessionClosed(String sessionToken);
+}
diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/ISessionManager.java b/authentication/source/java/ch/systemsx/cisd/authentication/ISessionManager.java
index e08049acc0321e52542b78df54f3fc4da79e8c16..30aa716f670e58a0e70abc6dbacbf9a504ac7274 100644
--- a/authentication/source/java/ch/systemsx/cisd/authentication/ISessionManager.java
+++ b/authentication/source/java/ch/systemsx/cisd/authentication/ISessionManager.java
@@ -75,5 +75,7 @@ public interface ISessionManager<T extends BasicSession> extends IRemoteHostProv
      * </p>
      */
     public T tryGetSession(final String sessionToken);
+    
+    public void addListener(ISessionActionListener listener);
 
 }
\ No newline at end of file
diff --git a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/DefaultSessionManagerTest.java b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/DefaultSessionManagerTest.java
index 78ed2c035dfb0d7a263d661c437f97504bc80f4a..8704bf905bc52b5709c2d374c306bad517d9c637 100644
--- a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/DefaultSessionManagerTest.java
+++ b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/DefaultSessionManagerTest.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Level;
@@ -40,6 +41,7 @@ import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.logging.BufferedAppender;
 import ch.systemsx.cisd.common.server.IRemoteHostProvider;
+import ch.systemsx.cisd.common.test.RecordingMatcher;
 import ch.systemsx.cisd.common.test.RetryTen;
 import ch.systemsx.cisd.common.test.TestReportCleaner;
 
@@ -78,6 +80,8 @@ public class DefaultSessionManagerTest
 
     private Sequence sequence;
 
+    private ISessionActionListener sessionActionListener;
+
     private void assertExceptionMessageForInvalidSessionToken(final UserFailureException ex)
     {
         final String message = ex.getMessage();
@@ -94,6 +98,7 @@ public class DefaultSessionManagerTest
         authenticationService = context.mock(IAuthenticationService.class);
         remoteHostProvider = context.mock(IRemoteHostProvider.class);
         principalProvider = context.mock(IPrincipalProvider.class);
+        sessionActionListener = context.mock(ISessionActionListener.class);
         sequence = context.sequence("sequence");
         context.checking(new Expectations()
             {
@@ -232,6 +237,7 @@ public class DefaultSessionManagerTest
     public void testLimitedNumberOfSession()
     {
         final String user = "bla";
+        RecordingMatcher<BasicSession> recordingSessionMatcher = new RecordingMatcher<BasicSession>();
         context.checking(new Expectations()
             {
                 {
@@ -243,7 +249,7 @@ public class DefaultSessionManagerTest
                     exactly(3).of(authenticationService).tryGetAndAuthenticateUser(user, "blub");
                     will(returnValue(principal));
 
-                    for (int i = 0; i < 2; i++)
+                    for (int i = 0; i < 3; i++)
                     {
                         one(sessionFactory).create(with(any(String.class)), with(equal(user)),
                                 with(equal(principal)), with(equal(REMOTE_HOST)),
@@ -253,26 +259,24 @@ public class DefaultSessionManagerTest
                         will(returnValue(session));
                         inSequence(sequence);
 
-                        one(prefixGenerator).createPrefix(session);
+                        one(prefixGenerator).createPrefix(with(recordingSessionMatcher));
                         will(returnValue("[USER:'" + user + "', HOST:'remote-host']"));
                     }
-
-                    one(prefixGenerator).createPrefix("bla", REMOTE_HOST);
-                    will(returnValue("[USER:'bla', HOST:'remote-host']"));
+                    one(prefixGenerator).createPrefix(with(recordingSessionMatcher));
+                    will(returnValue("[USER:'" + user + "', HOST:'remote-host']"));
+                    
+                    one(sessionActionListener).sessionClosed("bla-1");
                 }
             });
         sessionManager = createSessionManager(SESSION_EXPIRATION_PERIOD_MINUTES, Collections.singletonMap("bla", 2));
+        sessionManager.addListener(sessionActionListener);
 
         assertEquals("bla-1", sessionManager.tryToOpenSession("bla", "blub"));
         assertEquals("bla-2", sessionManager.tryToOpenSession("bla", "blub"));
-        try
-        {
-            assertEquals("bla-3", sessionManager.tryToOpenSession("bla", "blub"));
-            fail("UserFailureException expected");
-        } catch (UserFailureException e)
-        {
-            assertEquals("2 open session(s) but only 2 session(s) are allowed for user bla.", e.getMessage());
-        }
+        assertEquals("bla-3", sessionManager.tryToOpenSession("bla", "blub"));
+        assertEquals("[bla-1, bla-2, bla-1, bla-3]", 
+                recordingSessionMatcher.getRecordedObjects().stream().map(s -> s.getSessionToken())
+                .collect(Collectors.toList()).toString());
 
         context.assertIsSatisfied();
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
index c1d3ce70d524a95cd0c556d0eeaccf5a62273470..0976d7a0f467a11a7718dcacdf30e4fb5776250d 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
@@ -36,6 +36,7 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 
 import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.authentication.ISessionActionListener;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
@@ -73,8 +74,10 @@ import ch.systemsx.cisd.openbis.generic.client.web.server.translator.ResultSetTr
 import ch.systemsx.cisd.openbis.generic.client.web.server.translator.UserFailureExceptionTranslator;
 import ch.systemsx.cisd.openbis.generic.client.web.server.util.TableModelUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.server.util.XMLPropertyTransformer;
+import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
 import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
+import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.WebClientConfigurationProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
@@ -116,6 +119,29 @@ public abstract class AbstractClientService implements IClientService,
     @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME)
     private ExposablePropertyPlaceholderConfigurer configurer;
 
+    @Resource(name = ComponentNames.SESSION_MANAGER)
+    private IOpenBisSessionManager sessionManager;
+
+    private final Map<String, HttpSession> httpSessionsBySessionToken = new HashMap<>();
+
+    private ISessionActionListener sessionActionListener = new ISessionActionListener()
+        {
+            @Override
+            public void sessionClosed(String sessionToken)
+            {
+                synchronized (httpSessionsBySessionToken)
+                {
+                    HttpSession httpSession = httpSessionsBySessionToken.remove(sessionToken);
+                    logout(null, true, httpSession, sessionToken);
+                    if (httpSession != null)
+                    {
+                        operationLog.info("Session " + sessionToken + " closed. "
+                                + "httpSessionsBySessionToken.size() = " + httpSessionsBySessionToken.size());
+                    }
+                }
+            }
+        };
+
     @Autowired
     private TableDataCache<String, Object> tableDataCache;
 
@@ -653,6 +679,12 @@ public abstract class AbstractClientService implements IClientService,
             return null;
         }
         final HttpSession httpSession = createHttpSession();
+        synchronized (httpSessionsBySessionToken)
+        {
+            httpSessionsBySessionToken.put(session.getSessionToken(), httpSession);
+            operationLog.info("httpSessionsBySessionToken.size() = " + httpSessionsBySessionToken.size());
+        }
+
         // Expiration time of httpSession is 10 seconds less than of session
         final int sessionExpirationTimeInMillis = session.getSessionExpirationTime();
         final int sessionExpirationTimeInSeconds = sessionExpirationTimeInMillis / 1000;
@@ -772,26 +804,31 @@ public abstract class AbstractClientService implements IClientService,
         try
         {
             final HttpSession httpSession = getHttpSession();
-            if (httpSession != null)
+            String sessionToken = getSessionToken();
+            logout(displaySettings, simpleViewMode, httpSession, sessionToken);
+        } catch (Exception e)
+        {
+            operationLog.info("logout exception: " + e);
+        }
+    }
+
+    private void logout(DisplaySettings displaySettings, boolean simpleViewMode, final HttpSession httpSession, String sessionToken)
+    {
+        if (httpSession != null)
+        {
+            httpSession.removeAttribute(SessionConstants.OPENBIS_SESSION_TOKEN_ATTRIBUTE_KEY);
+            httpSession.removeAttribute(SessionConstants.OPENBIS_SERVER_ATTRIBUTE_KEY);
+            httpSession.removeAttribute(SessionConstants.OPENBIS_RESULT_SET_MANAGER);
+            httpSession.removeAttribute(SessionConstants.OPENBIS_EXPORT_MANAGER);
+            httpSession.invalidate();
+            IServer server = getServer();
+            if (simpleViewMode == false)
             {
-                String sessionToken = getSessionToken();
-                httpSession.removeAttribute(SessionConstants.OPENBIS_SESSION_TOKEN_ATTRIBUTE_KEY);
-                httpSession.removeAttribute(SessionConstants.OPENBIS_SERVER_ATTRIBUTE_KEY);
-                httpSession.removeAttribute(SessionConstants.OPENBIS_RESULT_SET_MANAGER);
-                httpSession.removeAttribute(SessionConstants.OPENBIS_EXPORT_MANAGER);
-                httpSession.invalidate();
-                IServer server = getServer();
-                if (simpleViewMode == false)
-                {
-                    // only save settings for "normal" view
-                    int maxEntityVisits = getWebClientConfiguration().getMaxEntityVisits();
-                    server.saveDisplaySettings(sessionToken, displaySettings, maxEntityVisits);
-                }
-                server.logout(sessionToken);
+                // only save settings for "normal" view
+                int maxEntityVisits = getWebClientConfiguration().getMaxEntityVisits();
+                server.saveDisplaySettings(sessionToken, displaySettings, maxEntityVisits);
             }
-        } catch (final UserFailureException e)
-        {
-            // Just ignore it because we are logging out anyway.
+            server.logout(sessionToken);
         }
     }
 
@@ -832,5 +869,6 @@ public abstract class AbstractClientService implements IClientService,
     public void setApplicationContext(ApplicationContext applicationContext)
     {
         this.applicationContext = applicationContext;
+        sessionManager.addListener(sessionActionListener);
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index 5952c837f6beff9e6fb95c9f45147a930ab5e996..3ce90cd554e611f71540f4cc2fe9b94742041c05 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -29,6 +29,7 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 
 import org.apache.commons.lang3.StringUtils;
@@ -39,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
 import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
 import ch.systemsx.cisd.authentication.DefaultSessionManager;
 import ch.systemsx.cisd.authentication.IPrincipalProvider;
+import ch.systemsx.cisd.authentication.ISessionActionListener;
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.authentication.Principal;
 import ch.systemsx.cisd.common.action.IDelegatedActionWithResult;
@@ -178,6 +180,15 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
     private IApplicationServerApi v3Api;
 
     protected String CISDHelpdeskEmail;
+    
+    private ISessionActionListener sessionActionListener = new ISessionActionListener()
+    {
+        @Override
+        public void sessionClosed(String sessionToken)
+        {
+            logout(sessionToken);
+        }
+    };
 
     protected AbstractServer()
     {
@@ -204,6 +215,12 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         this.sampleTypeSlaveServerPlugin = sampleTypeSlaveServerPlugin;
         this.dataSetTypeSlaveServerPlugin = dataSetTypeSlaveServerPlugin;
     }
+    
+    @PostConstruct
+    public void registerAtSessionManager()
+    {
+        sessionManager.addListener(sessionActionListener);
+    }
 
     // For unit tests - in production Spring will inject this object.
     public void setDisplaySettingsProvider(DisplaySettingsProvider displaySettingsProvider)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
index 8d5e02c3dee9b580ff0fcfc4c79a2ec9a6a53c08..9d642c3f4903ae4f33d7df972a4b07a95c487e96 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -31,31 +33,24 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 
 /**
  * Helper class to be used in initialize-master-data.py.
- * 
+ *
  * @author Franz-Josef Elmer
  */
-public class MasterDataRegistrationHelper
-{
+public class MasterDataRegistrationHelper {
     private static Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, MasterDataRegistrationHelper.class);
-    
+
     private File masterDataFolder;
 
-    public MasterDataRegistrationHelper(Collection<?> systemPaths)
-    {
-        for (Object systemPath : systemPaths)
-        {
-            if (systemPath != null)
-            {
+    public MasterDataRegistrationHelper(Collection<?> systemPaths) {
+        for (Object systemPath : systemPaths) {
+            if (systemPath != null) {
                 String systemPathString = String.valueOf(systemPath);
-                if (systemPathString.contains("core-plugins"))
-                {
+                if (systemPathString.contains("core-plugins")) {
                     masterDataFolder = new File(new File(systemPathString), "master-data");
-                    if (masterDataFolder.exists() == false)
-                    {
+                    if (masterDataFolder.exists() == false) {
                         throw new IllegalArgumentException("Folder does not exist: " + masterDataFolder.getAbsolutePath());
                     }
-                    if (masterDataFolder.isFile())
-                    {
+                    if (masterDataFolder.isFile()) {
                         throw new IllegalArgumentException("Is not a folder but a file: " + masterDataFolder.getAbsolutePath());
                     }
                     operationLog.info("Master data folder: " + masterDataFolder.getAbsolutePath());
@@ -66,14 +61,11 @@ public class MasterDataRegistrationHelper
         throw new IllegalArgumentException("Does not contain path to the core plugin: " + systemPaths);
     }
 
-    public List<byte[]> listXlsByteArrays()
-    {
+    public List<byte[]> listXlsByteArrays() {
         List<byte[]> result = new ArrayList<>();
-        for (File file : masterDataFolder.listFiles())
-        {
+        for (File file : masterDataFolder.listFiles()) {
             String name = file.getName();
-            if (name.endsWith(".xls") || name.endsWith(".xlsx"))
-            {
+            if (name.endsWith(".xls") || name.endsWith(".xlsx")) {
                 operationLog.info("load master data " + file.getName());
                 result.add(FileUtilities.loadToByteArray(file));
             }
@@ -81,34 +73,39 @@ public class MasterDataRegistrationHelper
         return result;
     }
 
-    public Map<String, String> getAllScripts()
-    {
+    public List<byte[]> listCsvByteArrays() throws IOException {
+        List<byte[]> result = new ArrayList<>();
+        for (File file : masterDataFolder.listFiles()) {
+            String name = file.getName();
+            if (name.endsWith(".csv")) {
+                operationLog.info("load master data " + file.getName());
+                result.add(Files.readAllBytes(file.toPath()));
+            }
+        }
+        return result;
+    }
+
+    public Map<String, String> getAllScripts() {
         Map<String, String> result = new TreeMap<>();
         File scriptsFolder = new File(masterDataFolder, "scripts");
-        if (scriptsFolder.isDirectory())
-        {
+        if (scriptsFolder.isDirectory()) {
             gatherScripts(result, scriptsFolder, scriptsFolder);
         }
         return result;
     }
 
-    private void gatherScripts(Map<String, String> scripts, File rootFolder, File file)
-    {
-        if (file.isFile())
-        {
+    private void gatherScripts(Map<String, String> scripts, File rootFolder, File file) {
+        if (file.isFile()) {
             String scriptPath = FileUtilities.getRelativeFilePath(rootFolder, file);
             scripts.put(scriptPath, FileUtilities.loadToString(file));
             operationLog.info("Script " + scriptPath + " loaded");
         }
-        if (file.isDirectory())
-        {
+        if (file.isDirectory()) {
             File[] files = file.listFiles();
-            for (File child : files)
-            {
+            for (File child : files) {
                 gatherScripts(scripts, rootFolder, child);
             }
         }
     }
 
-    
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
index 6fe6ce85a34339948828f0fc37526e39efc73ac1..672ac400beb4e5427f8154a07f98d97679c906e2 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
@@ -2045,6 +2045,17 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 			});
 		}
 
+        this.isSessionActive = function() {
+            var thisFacade = this;
+            return thisFacade._private.ajaxRequest({
+                url : openbisUrl,
+                data : {
+                    "method" : "isSessionActive",
+                    "params" : [ thisFacade._private.sessionToken ]
+                }
+            });
+        }
+
 		this.getDataStoreFacade = function() {
 			var dataStoreCodes = [];
 			for (var i = 0; i < arguments.length; i++) {
diff --git a/openbis_ng_ui/.eslintrc b/openbis_ng_ui/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..5b842c72c51ed8b5a6b52cb77356abc8d5fe9c79
--- /dev/null
+++ b/openbis_ng_ui/.eslintrc
@@ -0,0 +1,41 @@
+{
+  "root": true,
+  "parser": "babel-eslint",
+  "parserOptions": {
+    "ecmaVersion": 2017,
+    "sourceType": "module",
+    "ecmaFeatures": {
+      "jsx": true
+    }
+  },
+  "env": {
+    "browser": true,
+    "jest": true,
+    "es6": true
+  },
+  "extends": [
+    "eslint:recommended",
+    "plugin:react/recommended",
+    "plugin:prettier/recommended"
+  ],
+  "plugins": ["react", "prettier"],
+  "settings": {
+    "react": {
+      "createClass": "createReactClass",
+      "pragma": "React",
+      "version": "16.4.2"
+    },
+    "propWrapperFunctions": ["forbidExtraProps"]
+  },
+  "rules": {
+    "react/jsx-uses-react": "error",
+    "react/jsx-uses-vars": "error",
+    "react/prop-types": "off",
+    "prettier/prettier": "error",
+
+    "eqeqeq": ["error", "always"],
+
+    // override default options for rules from base configurations
+    "no-cond-assign": ["error", "always"]
+  }
+}
diff --git a/openbis_ng_ui/.eslintrc.js b/openbis_ng_ui/.eslintrc.js
deleted file mode 100644
index 4a2639476b096e99ca1da0a2920b18f6bc15564f..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/.eslintrc.js
+++ /dev/null
@@ -1,47 +0,0 @@
-module.exports = {
-  root: true,
-  parser: 'babel-eslint',
-  parserOptions: {
-    ecmaVersion: 2017,
-    sourceType: 'module',
-    ecmaFeatures: {
-      jsx: true
-    }
-  },
-  env: {
-    browser: true,
-    jest: true,
-    es6: true
-  },
-  extends: [
-    'eslint:recommended',
-    'plugin:react/recommended'
-  ],
-  plugins: [
-    'react'
-  ],
-  settings: {
-    react: {
-      createClass: 'createReactClass',
-      pragma: 'React',
-      version: '16.4.2'
-    },
-    propWrapperFunctions: ['forbidExtraProps']
-  },
-  rules: {
-    'react/jsx-uses-react': 'error',
-    'react/jsx-uses-vars': 'error',
-
-    'indent': ['error', 2, { 'SwitchCase': 1 }],
-    'key-spacing': ["error", { "mode": "strict" }],
-    'linebreak-style': ['error', 'unix'],
-    'quotes': ['error', 'single'],
-    'semi': ['error', 'never'],
-    'eqeqeq': ['error', 'always'],
-
-    'react/prop-types': 'off',
-
-    // override default options for rules from base configurations
-    'no-cond-assign': ['error', 'always'],
-  }
-}
diff --git a/openbis_ng_ui/.prettierrc b/openbis_ng_ui/.prettierrc
new file mode 100644
index 0000000000000000000000000000000000000000..b10e63c106124b2ee980fbee8fef8a829005b55b
--- /dev/null
+++ b/openbis_ng_ui/.prettierrc
@@ -0,0 +1,6 @@
+{
+  "semi": false,
+  "endOfLine": "lf",
+  "singleQuote": true,
+  "jsxSingleQuote": true
+}
diff --git a/openbis_ng_ui/README.md b/openbis_ng_ui/README.md
index 963b4be6c126e37ee27356c89579c16d4c0d9860..a8bd209911a38c2cf38b3fbe28afb62fb01b3f9c 100644
--- a/openbis_ng_ui/README.md
+++ b/openbis_ng_ui/README.md
@@ -20,6 +20,10 @@
 
 1. Under "IntelliJ IDEA" -> "Preferences" -> "Languages and Frameworks" -> Javascript, set the language version to ECMAScript 6.
 
+## Setting up Visual Studio Code (alternative to IntelliJ Idea)
+
+Install "ESLint" and "Prettier - Code formatter" extensions.
+
 ## Login into Vagrant to see the dev server output
 
 1. vagrant ssh
@@ -30,28 +34,30 @@
 
 On Ubuntu (and maybe on other Linux distributions, too), you might get errors like this when running "vagrant up":
 
-  terminate called after throwing an instance of 'std::runtime_error'
-    what():  Could not add watch
+terminate called after throwing an instance of 'std::runtime_error'
+what(): Could not add watch
 
 This situation will prevent the development environment from working properly. The reason for the error is https://github.com/mhallin/vagrant-notify-forwarder/issues/5. It can be fixed by increasing the maximum number of watches on the host system. On Ubuntu, this is done like this:
 
-  echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
+echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
 
 ## "Jest" automated tests
 
 Running tests
 
-  npm run test
+npm run test
 
 Debugging tests
 
-  1. execute in command line:
+1. execute in command line:
+
 
     node --inspect-brk ./node_modules/.bin/jest --runInBand
 
-  2. put the following snippet of code in a line where the debugger should stop:
+2. put the following snippet of code in a line where the debugger should stop:
+
 
     /*eslint no-debugger: 0*/
     debugger
 
-  3. open "chrome://inspect" in Chrome and choose "Open dedicated DevTools for Node" to start debugging
+3. open "chrome://inspect" in Chrome and choose "Open dedicated DevTools for Node" to start debugging
diff --git a/openbis_ng_ui/babel.config.js b/openbis_ng_ui/babel.config.js
index 8f5ca5e35a7fba4fd2b08c31cd02abf0d8b83509..6db8530ff2a276006956c90296140bcb41a5f853 100644
--- a/openbis_ng_ui/babel.config.js
+++ b/openbis_ng_ui/babel.config.js
@@ -1,11 +1,8 @@
 /* eslint-disable-next-line no-undef */
-module.exports = function (api) {
+module.exports = function(api) {
   api.cache(true)
 
-  const presets = [
-    '@babel/preset-env',
-    '@babel/preset-react',
-  ]
+  const presets = ['@babel/preset-env', '@babel/preset-react']
 
   const plugins = [
     '@babel/plugin-transform-runtime',
diff --git a/openbis_ng_ui/index.html b/openbis_ng_ui/index.html
index 4580046bc8c92ce9ea841e9aa87705b4184d01d1..8d6d4ada6e5414d2764a2559b5414a4574c6d966 100644
--- a/openbis_ng_ui/index.html
+++ b/openbis_ng_ui/index.html
@@ -2,18 +2,19 @@
 <html lang="en" style="height: 100%;">
   <head>
     <title>openBIS / React</title>
-    <meta charset="utf-8">
+    <meta charset="utf-8" />
     <link rel="shortcut icon" href="#" />
     <style>
       body {
         height: 100%;
-        min-height:100%;
+        min-height: 100%;
         margin: 0px 0px 0px 0px;
       }
       #app {
         height: 100%;
       }
-      #app > div, #app > div > div {
+      #app > div,
+      #app > div > div {
         height: 100%;
       }
       #error {
@@ -23,15 +24,18 @@
     <script>
       function loadError() {
         window.onload = function() {
-          let text = "Error: Could not connect to openBIS."
-          let h2 = document.createElement("h2")
+          let text = 'Error: Could not connect to openBIS.'
+          let h2 = document.createElement('h2')
           var textnode = document.createTextNode(text)
           h2.appendChild(textnode)
-          document.getElementById("error").appendChild(h2)
+          document.getElementById('error').appendChild(h2)
         }
       }
     </script>
-    <script src="/openbis/resources/api/v3/config.js" onerror="loadError()"></script>
+    <script
+      src="/openbis/resources/api/v3/config.js"
+      onerror="loadError()"
+    ></script>
     <script src="/openbis/resources/api/v3/require.js"></script>
   </head>
   <body>
diff --git a/openbis_ng_ui/jest.config.js b/openbis_ng_ui/jest.config.js
index 82e9d638d412a254a477bad383680cb442fdd40e..79dfc00c8043c5af573a3d0488e48b532c95c9a8 100644
--- a/openbis_ng_ui/jest.config.js
+++ b/openbis_ng_ui/jest.config.js
@@ -1,10 +1,5 @@
 /* eslint-disable-next-line no-undef */
 module.exports = {
-  reporters: [
-    'default',
-    'jest-junit'
-  ],
-  setupFilesAfterEnv: [
-    '<rootDir>srcTest/setupTests.js'
-  ]
+  reporters: ['default', 'jest-junit'],
+  setupFilesAfterEnv: ['<rootDir>srcTest/setupTests.js']
 }
diff --git a/openbis_ng_ui/package.json b/openbis_ng_ui/package.json
index ac896db494e247fdf5b28971219e8ce5eef1593c..77bd25d834a7a673400e987cbd0b022d04481d5d 100644
--- a/openbis_ng_ui/package.json
+++ b/openbis_ng_ui/package.json
@@ -39,17 +39,18 @@
     "enzyme": "^3.10.0",
     "enzyme-adapter-react-16": "^1.14.0",
     "eslint": "^6.0.1",
-    "eslint-config-standard": "^12.0.0",
+    "eslint-config-prettier": "^6.2.0",
     "eslint-plugin-import": "^2.18.0",
     "eslint-plugin-jest": "^22.7.2",
     "eslint-plugin-node": "^9.1.0",
+    "eslint-plugin-prettier": "^3.1.0",
     "eslint-plugin-promise": "^4.2.1",
     "eslint-plugin-react": "^7.14.2",
-    "eslint-plugin-standard": "^4.0.0",
     "file-loader": "^4.0.0",
     "html-webpack-plugin": "^3.2.0",
     "jest": "^24.8.0",
     "jest-junit": "^6.4.0",
+    "prettier": "^1.18.2",
     "raw-loader": "^3.0.0",
     "react-hot-loader": "^4.12.5",
     "react-loader": "^2.4.5",
diff --git a/openbis_ng_ui/src/common/consts/routes.js b/openbis_ng_ui/src/common/consts/routes.js
index a71bb3cbe20efb1a0ff988f92d57d3a9d6d9209b..c678fe62fda14759532a7ed7c850072fd1ac115b 100644
--- a/openbis_ng_ui/src/common/consts/routes.js
+++ b/openbis_ng_ui/src/common/consts/routes.js
@@ -4,56 +4,56 @@ import pathToRegexp from 'path-to-regexp'
 import * as pages from './pages.js'
 import * as objectTypes from './objectType.js'
 
-function doFormat(actualParams, pattern, requiredParams){
-  if(_.isMatch(actualParams, requiredParams)){
+function doFormat(actualParams, pattern, requiredParams) {
+  if (_.isMatch(actualParams, requiredParams)) {
     let toPath = pathToRegexp.compile(pattern)
     return {
       path: toPath(actualParams),
       match: Object.keys(requiredParams).length
     }
-  }else{
+  } else {
     return null
   }
 }
 
-function doParse(path, pattern, extraParams){
+function doParse(path, pattern, extraParams) {
   let match = matchPath(path, {
     path: pattern,
     exact: true,
     strict: false
   })
-  if(match){
+  if (match) {
     return {
       path: match.url,
       ...match.params,
       ...extraParams
     }
-  }else{
+  } else {
     return null
   }
 }
 
 const routes = {
   TYPES: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/types', {
         page: pages.TYPES
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/types', {
         page: pages.TYPES
       })
     }
   },
   TYPES_SEARCH: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/typesSearch/:id', {
         page: pages.TYPES,
         type: objectTypes.SEARCH
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/typesSearch/:id', {
         page: pages.TYPES,
         type: objectTypes.SEARCH
@@ -61,13 +61,13 @@ const routes = {
     }
   },
   OBJECT_TYPE: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/objectType/:id', {
         page: pages.TYPES,
         type: objectTypes.OBJECT_TYPE
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/objectType/:id', {
         page: pages.TYPES,
         type: objectTypes.OBJECT_TYPE
@@ -75,13 +75,13 @@ const routes = {
     }
   },
   COLLECTION_TYPE: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/collectionType/:id', {
         page: pages.TYPES,
         type: objectTypes.COLLECTION_TYPE
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/collectionType/:id', {
         page: pages.TYPES,
         type: objectTypes.COLLECTION_TYPE
@@ -89,13 +89,13 @@ const routes = {
     }
   },
   DATA_SET_TYPE: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/datasetType/:id', {
         page: pages.TYPES,
         type: objectTypes.DATA_SET_TYPE
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/datasetType/:id', {
         page: pages.TYPES,
         type: objectTypes.DATA_SET_TYPE
@@ -103,13 +103,13 @@ const routes = {
     }
   },
   MATERIAL_TYPE: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/materialType/:id', {
         page: pages.TYPES,
         type: objectTypes.MATERIAL_TYPE
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/materialType/:id', {
         page: pages.TYPES,
         type: objectTypes.MATERIAL_TYPE
@@ -117,25 +117,25 @@ const routes = {
     }
   },
   USERS: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/users', {
         page: pages.USERS
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/users', {
         page: pages.USERS
       })
     }
   },
   USERS_SEARCH: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/usersSearch/:id', {
         page: pages.USERS,
         type: objectTypes.SEARCH
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/usersSearch/:id', {
         page: pages.USERS,
         type: objectTypes.SEARCH
@@ -143,13 +143,13 @@ const routes = {
     }
   },
   USER: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/user/:id', {
         page: pages.USERS,
         type: objectTypes.USER
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/user/:id', {
         page: pages.USERS,
         type: objectTypes.USER
@@ -157,13 +157,13 @@ const routes = {
     }
   },
   GROUP: {
-    format: (params) => {
+    format: params => {
       return doFormat(params, '/group/:id', {
         page: pages.USERS,
         type: objectTypes.GROUP
       })
     },
-    parse: (path) => {
+    parse: path => {
       return doParse(path, '/group/:id', {
         page: pages.USERS,
         type: objectTypes.GROUP
@@ -171,13 +171,13 @@ const routes = {
     }
   },
   DEFAULT: {
-    format: (params) => {
+    format: () => {
       return {
         path: '/',
         match: 0
       }
     },
-    parse: (path) => {
+    parse: () => {
       return {
         path: '/',
         page: pages.TYPES
@@ -186,26 +186,30 @@ const routes = {
   }
 }
 
-function format(params){
+function format(params) {
   let keys = Object.keys(routes)
   let bestPath = null
   let bestMatch = 0
-  for(let i = 0; i < keys.length; i++){
+  for (let i = 0; i < keys.length; i++) {
     let route = routes[keys[i]]
-    let result = route.format(params)
-    if(result && result.match > bestMatch){
-      bestPath = result.path
+    try {
+      let result = route.format(params)
+      if (result && result.match > bestMatch) {
+        bestPath = result.path
+      }
+    } catch (err) {
+      // ignore problems with incorrect params
     }
   }
   return bestPath
 }
 
-function parse(path){
+function parse(path) {
   let keys = Object.keys(routes)
-  for(let i = 0; i < keys.length; i++){
+  for (let i = 0; i < keys.length; i++) {
     let route = routes[keys[i]]
     let params = route.parse(path)
-    if(params){
+    if (params) {
       return params
     }
   }
diff --git a/openbis_ng_ui/src/common/logger.js b/openbis_ng_ui/src/common/logger.js
index c6ace92add51173033f875fbb0576bc7e2e21861..7d792c6b9522a1cff6045ab281b5baf64b1e427e 100644
--- a/openbis_ng_ui/src/common/logger.js
+++ b/openbis_ng_ui/src/common/logger.js
@@ -1,35 +1,42 @@
-export const ERROR = {name: 'ERROR', importance: 4}
-export const INFO = {name: 'INFO', importance: 3}
-export const DEBUG = {name: 'DEBUG', importance: 2}
-export const TRACE = {name: 'TRACE', importance: 1}
+export const ERROR = { name: 'ERROR', importance: 4 }
+export const INFO = { name: 'INFO', importance: 3 }
+export const DEBUG = { name: 'DEBUG', importance: 2 }
+export const TRACE = { name: 'TRACE', importance: 1 }
 
 let currentLevel = INFO
 
-let isLevelEnabled = (level) => {
+let isLevelEnabled = level => {
   return level.importance >= currentLevel.importance
 }
 
 let log = (level, message, ...params) => {
-  if(isLevelEnabled(level)){
+  if (isLevelEnabled(level)) {
     // eslint-disable-next-line no-console
     console.log(message, ...params)
   }
 }
 
 let group = (level, message, ...params) => {
-  if(isLevelEnabled(level)){
+  if (isLevelEnabled(level)) {
     // eslint-disable-next-line no-console
     console.group(message, ...params)
   }
 }
 
-let groupEnd = (level) => {
-  if(isLevelEnabled(level)){
+let groupEnd = level => {
+  if (isLevelEnabled(level)) {
     // eslint-disable-next-line no-console
     console.groupEnd()
   }
 }
 
 export default {
-  ERROR, INFO, DEBUG, TRACE, log, group, groupEnd, isLevelEnabled
+  ERROR,
+  INFO,
+  DEBUG,
+  TRACE,
+  log,
+  group,
+  groupEnd,
+  isLevelEnabled
 }
diff --git a/openbis_ng_ui/src/common/util.js b/openbis_ng_ui/src/common/util.js
index 22c7644df4d3d0fa3f51f2fccba36fbf275a4ed5..0970182dc5ffa99fa42242011acd2cf71e5fc37a 100644
--- a/openbis_ng_ui/src/common/util.js
+++ b/openbis_ng_ui/src/common/util.js
@@ -1,3 +1,3 @@
-export function classNames(...classNames){
+export function classNames(...classNames) {
   return classNames.filter(className => className).join(' ')
 }
diff --git a/openbis_ng_ui/src/components/App.jsx b/openbis_ng_ui/src/components/App.jsx
index 3f19e6a3d8777dba14e4573e6d820ca00c19674f..50d7eae97bbcad388db671ecfdbce379a3d84d54 100644
--- a/openbis_ng_ui/src/components/App.jsx
+++ b/openbis_ng_ui/src/components/App.jsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import _ from 'lodash'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../common/logger.js'
 import * as util from '../common/util.js'
 import * as pages from '../common/consts/pages.js'
@@ -31,16 +31,16 @@ const styles = {
     display: 'flex'
   },
   hidden: {
-    display: 'none',
+    display: 'none'
   }
 }
 
 const pageToComponent = {
   [pages.TYPES]: Types,
-  [pages.USERS]: Users,
+  [pages.USERS]: Users
 }
 
-function mapStateToProps(state){
+function mapStateToProps(state) {
   return {
     loading: selectors.getLoading(state),
     session: selectors.getSession(state),
@@ -49,16 +49,19 @@ function mapStateToProps(state){
   }
 }
 
-function mapDispatchToProps(dispatch){
+function mapDispatchToProps(dispatch) {
   return {
-    init: () => { dispatch(actions.init()) },
-    errorClosed: () => { dispatch(actions.errorChange(null)) }
+    init: () => {
+      dispatch(actions.init())
+    },
+    errorClosed: () => {
+      dispatch(actions.errorChange(null))
+    }
   }
 }
 
 class App extends React.Component {
-
-  componentDidMount(){
+  componentDidMount() {
     this.props.init()
   }
 
@@ -74,32 +77,39 @@ class App extends React.Component {
     )
   }
 
-  renderPage(){
+  renderPage() {
     const classes = this.props.classes
 
-    if(this.props.session){
+    if (this.props.session) {
       return (
         <div className={classes.container}>
           <Menu page={this.props.currentPage} />
-          {
-            _.map(pageToComponent, (PageComponent, page) => {
-              let visible = this.props.currentPage === page
-              return (
-                <div key={page} className={util.classNames(classes.page, visible ? classes.visible : classes.hidden)}>
-                  <PageComponent />
-                </div>
-              )
-            })
-          }
+          {_.map(pageToComponent, (PageComponent, page) => {
+            let visible = this.props.currentPage === page
+            return (
+              <div
+                key={page}
+                className={util.classNames(
+                  classes.page,
+                  visible ? classes.visible : classes.hidden
+                )}
+              >
+                <PageComponent />
+              </div>
+            )
+          })}
         </div>
       )
-    }else{
-      return <Login/>
+    } else {
+      return <Login />
     }
   }
 }
 
 export default _.flow(
-  connect(mapStateToProps, mapDispatchToProps),
+  connect(
+    mapStateToProps,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(App)
diff --git a/openbis_ng_ui/src/components/common/browser/Browser.jsx b/openbis_ng_ui/src/components/common/browser/Browser.jsx
index 2db0eba9cf295468381f4af8bffff69c1ed03025..cdd22813f33ea8617638f59385fb57a4f92dd6cb 100644
--- a/openbis_ng_ui/src/components/common/browser/Browser.jsx
+++ b/openbis_ng_ui/src/components/common/browser/Browser.jsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import _ from 'lodash'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 import * as selectors from '../../../store/selectors/selectors.js'
 import * as actions from '../../../store/actions/actions.js'
@@ -17,7 +17,7 @@ const styles = {
   }
 }
 
-function mapStateToProps(){
+function mapStateToProps() {
   const getBrowserNodes = selectors.createGetBrowserNodes()
   return (state, ownProps) => {
     return {
@@ -27,19 +27,28 @@ function mapStateToProps(){
   }
 }
 
-function mapDispatchToProps(dispatch, ownProps){
+function mapDispatchToProps(dispatch, ownProps) {
   return {
-    init: () => { dispatch(actions.browserInit(ownProps.page)) },
-    filterChange: (filter) => { dispatch(actions.browserFilterChange(ownProps.page, filter)) },
-    nodeSelect: (id) => {   dispatch(actions.browserNodeSelect(ownProps.page, id)) },
-    nodeExpand: (id) => { dispatch(actions.browserNodeExpand(ownProps.page, id)) },
-    nodeCollapse: (id) => { dispatch(actions.browserNodeCollapse(ownProps.page, id)) }
+    init: () => {
+      dispatch(actions.browserInit(ownProps.page))
+    },
+    filterChange: filter => {
+      dispatch(actions.browserFilterChange(ownProps.page, filter))
+    },
+    nodeSelect: id => {
+      dispatch(actions.browserNodeSelect(ownProps.page, id))
+    },
+    nodeExpand: id => {
+      dispatch(actions.browserNodeExpand(ownProps.page, id))
+    },
+    nodeCollapse: id => {
+      dispatch(actions.browserNodeCollapse(ownProps.page, id))
+    }
   }
 }
 
 class Browser extends React.PureComponent {
-
-  componentDidMount(){
+  componentDidMount() {
     this.props.init()
   }
 
@@ -64,10 +73,12 @@ class Browser extends React.PureComponent {
       </div>
     )
   }
-
 }
 
 export default _.flow(
-  connect(mapStateToProps, mapDispatchToProps),
+  connect(
+    mapStateToProps,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(Browser)
diff --git a/openbis_ng_ui/src/components/common/browser/BrowserNode.jsx b/openbis_ng_ui/src/components/common/browser/BrowserNode.jsx
index 2844eecc2a71beaf7eb266494f8d66853333adc3..0bee6b7e4702664df364b990cd8bcd1429acd8ee 100644
--- a/openbis_ng_ui/src/components/common/browser/BrowserNode.jsx
+++ b/openbis_ng_ui/src/components/common/browser/BrowserNode.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import _ from 'lodash'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import ListItem from '@material-ui/core/ListItem'
 import ListItemIcon from '@material-ui/core/ListItemIcon'
 import ListItemText from '@material-ui/core/ListItemText'
@@ -18,71 +18,90 @@ const styles = {
 }
 
 class BrowserNode extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'BrowserNode.render')
 
-    const {node, level} = this.props
+    const { node, level } = this.props
 
-    return (<div>
-      <ListItem
-        button
-        selected={node.selected}
-        onClick={() => this.props.nodeSelect(node.id)}
-        style={{paddingLeft: level * 24 + 'px'}}>
-        {this.renderIcon(node)}
-        {this.renderText(node)}
-      </ListItem>
-      {node.children && node.children.length > 0 &&
-            <Collapse in={node.expanded} mountOnEnter={true} unmountOnExit={true}>
-              <BrowserNodes
-                nodes={node.children}
-                nodeSelect={this.props.nodeSelect}
-                nodeCollapse={this.props.nodeCollapse}
-                nodeExpand={this.props.nodeExpand}
-                level={level + 1} />
-            </Collapse>}
-    </div>)
+    return (
+      <div>
+        <ListItem
+          button
+          selected={node.selected}
+          onClick={() => this.props.nodeSelect(node.id)}
+          style={{ paddingLeft: level * 24 + 'px' }}
+        >
+          {this.renderIcon(node)}
+          {this.renderText(node)}
+        </ListItem>
+        {node.children && node.children.length > 0 && (
+          <Collapse in={node.expanded} mountOnEnter={true} unmountOnExit={true}>
+            <BrowserNodes
+              nodes={node.children}
+              nodeSelect={this.props.nodeSelect}
+              nodeCollapse={this.props.nodeCollapse}
+              nodeExpand={this.props.nodeExpand}
+              level={level + 1}
+            />
+          </Collapse>
+        )}
+      </div>
+    )
   }
 
-  renderIcon(node){
+  renderIcon(node) {
     logger.log(logger.DEBUG, 'BrowserNode.renderIcon')
 
     const classes = this.props.classes
 
-    if(node.children && node.children.length > 0){
+    if (node.children && node.children.length > 0) {
       let icon = null
-      if(node.expanded){
-        icon = <ExpandMoreIcon onClick={(e) => {
-          e.stopPropagation()
-          this.props.nodeCollapse(node.id)
-        }}/>
-      }else{
-        icon = <ChevronRightIcon onClick={(e) => {
-          e.stopPropagation()
-          this.props.nodeExpand(node.id)
-        }}/>
+      if (node.expanded) {
+        icon = (
+          <ExpandMoreIcon
+            onClick={e => {
+              e.stopPropagation()
+              this.props.nodeCollapse(node.id)
+            }}
+          />
+        )
+      } else {
+        icon = (
+          <ChevronRightIcon
+            onClick={e => {
+              e.stopPropagation()
+              this.props.nodeExpand(node.id)
+            }}
+          />
+        )
       }
-      return <ListItemIcon classes={{
-        root: classes.icon
-      }}>{icon}</ListItemIcon>
-    }else{
-      return <ListItemIcon classes={{
-        root: classes.icon
-      }}><span></span></ListItemIcon>
+      return (
+        <ListItemIcon
+          classes={{
+            root: classes.icon
+          }}
+        >
+          {icon}
+        </ListItemIcon>
+      )
+    } else {
+      return (
+        <ListItemIcon
+          classes={{
+            root: classes.icon
+          }}
+        >
+          <span></span>
+        </ListItemIcon>
+      )
     }
   }
 
-  renderText(node){
+  renderText(node) {
     logger.log(logger.DEBUG, 'BrowserNode.renderText "' + node.text + '"')
 
-    return <ListItemText
-      primary={node.text}
-    />
+    return <ListItemText primary={node.text} />
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(BrowserNode)
+export default _.flow(withStyles(styles))(BrowserNode)
diff --git a/openbis_ng_ui/src/components/common/browser/BrowserNodes.jsx b/openbis_ng_ui/src/components/common/browser/BrowserNodes.jsx
index 299d1f385af0eecfd2b527fb5dd20d0e62336e99..626479e75e2bf70431c7bf63e199988fca71c79c 100644
--- a/openbis_ng_ui/src/components/common/browser/BrowserNodes.jsx
+++ b/openbis_ng_ui/src/components/common/browser/BrowserNodes.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import List from '@material-ui/core/List'
 import BrowserNode from './BrowserNode.jsx'
 import logger from '../../../common/logger.js'
@@ -12,22 +12,26 @@ const styles = () => ({
   },
   list: {
     paddingTop: '0',
-    paddingBottom: '0',
-  },
+    paddingBottom: '0'
+  }
 })
 
 class BrowserNodes extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'BrowserNodes.render')
 
     const classes = this.props.classes
 
     return (
-      <List className={util.classNames(classes.list, this.props.level === 0 ? classes.container : null)}>
-        {
-          this.props.nodes.map(node => {
-            return <BrowserNode
+      <List
+        className={util.classNames(
+          classes.list,
+          this.props.level === 0 ? classes.container : null
+        )}
+      >
+        {this.props.nodes.map(node => {
+          return (
+            <BrowserNode
               key={node.id}
               node={node}
               nodeSelect={this.props.nodeSelect}
@@ -35,12 +39,11 @@ class BrowserNodes extends React.Component {
               nodeExpand={this.props.nodeExpand}
               level={this.props.level}
             />
-          })
-        }
+          )
+        })}
       </List>
     )
   }
-
 }
 
 export default withStyles(styles)(BrowserNodes)
diff --git a/openbis_ng_ui/src/components/common/content/Content.jsx b/openbis_ng_ui/src/components/common/content/Content.jsx
index 5b760ed0277d536df0c955b3e73ca386cd87d749..edd4e403a6c009b7bc72664b86529002c6d92301 100644
--- a/openbis_ng_ui/src/components/common/content/Content.jsx
+++ b/openbis_ng_ui/src/components/common/content/Content.jsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import _ from 'lodash'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 import * as util from '../../../common/util.js'
 import * as selectors from '../../../store/selectors/selectors.js'
@@ -24,11 +24,11 @@ const styles = {
     display: 'block'
   },
   hidden: {
-    display: 'none',
+    display: 'none'
   }
 }
 
-function mapStateToProps(){
+function mapStateToProps() {
   const getSelectedObject = selectors.createGetSelectedObject()
   return (state, ownProps) => {
     return {
@@ -39,15 +39,18 @@ function mapStateToProps(){
   }
 }
 
-function mapDispatchToProps(dispatch, ownProps){
+function mapDispatchToProps(dispatch, ownProps) {
   return {
-    objectSelect: (type, id) => { dispatch(actions.objectOpen(ownProps.page, type, id)) },
-    objectClose: (type, id) => { dispatch(actions.objectClose(ownProps.page, type, id)) }
+    objectSelect: (type, id) => {
+      dispatch(actions.objectOpen(ownProps.page, type, id))
+    },
+    objectClose: (type, id) => {
+      dispatch(actions.objectClose(ownProps.page, type, id))
+    }
   }
 }
 
 class Content extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Content.render')
 
@@ -60,27 +63,35 @@ class Content extends React.Component {
           changedObjects={this.props.changedObjects}
           selectedObject={this.props.selectedObject}
           objectSelect={this.props.objectSelect}
-          objectClose={this.props.objectClose} />
-        {
-          this.props.openObjects.map(object => {
-            let ObjectComponent = this.props.objectTypeToComponent[object.type]
-            if(ObjectComponent){
-              let key = object.type + '/' + object.id
-              let visible = _.isEqual(object, this.props.selectedObject)
-              return (
-                <div key={key} className={util.classNames(classes.component, visible ? classes.visible : classes.hidden)}>
-                  <ObjectComponent objectId={object.id} />
-                </div>
-              )
-            }
-          })
-        }
+          objectClose={this.props.objectClose}
+        />
+        {this.props.openObjects.map(object => {
+          let ObjectComponent = this.props.objectTypeToComponent[object.type]
+          if (ObjectComponent) {
+            let key = object.type + '/' + object.id
+            let visible = _.isEqual(object, this.props.selectedObject)
+            return (
+              <div
+                key={key}
+                className={util.classNames(
+                  classes.component,
+                  visible ? classes.visible : classes.hidden
+                )}
+              >
+                <ObjectComponent objectId={object.id} />
+              </div>
+            )
+          }
+        })}
       </div>
     )
   }
 }
 
 export default _.flow(
-  connect(mapStateToProps, mapDispatchToProps),
+  connect(
+    mapStateToProps,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(Content)
diff --git a/openbis_ng_ui/src/components/common/content/ContentTabs.jsx b/openbis_ng_ui/src/components/common/content/ContentTabs.jsx
index d6eec630b753b230250f13d86a7ff549929463f1..6c2487d701de40b612d1ba1a4a6d0f84a2b23c2a 100644
--- a/openbis_ng_ui/src/components/common/content/ContentTabs.jsx
+++ b/openbis_ng_ui/src/components/common/content/ContentTabs.jsx
@@ -1,6 +1,6 @@
 import _ from 'lodash'
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import Tabs from '@material-ui/core/Tabs'
 import Tab from '@material-ui/core/Tab'
 import CloseIcon from '@material-ui/icons/Close'
@@ -12,16 +12,15 @@ const styles = {
     textTransform: 'none'
   },
   iconRoot: {
-    marginLeft: '6px',
+    marginLeft: '6px'
   },
   tabLabel: {
     display: 'inline-flex',
-    alignItems: 'center',
+    alignItems: 'center'
   }
 }
 
 class ContentTabs extends React.Component {
-
   handleTabChange = (event, value) => {
     let object = this.props.objects[value]
     this.props.objectSelect(object.type, object.id)
@@ -40,27 +39,28 @@ class ContentTabs extends React.Component {
     return (
       <Tabs
         value={_.findIndex(this.props.objects, this.props.selectedObject)}
-        variant="scrollable"
-        scrollButtons="on"
+        variant='scrollable'
+        scrollButtons='on'
         onChange={this.handleTabChange}
       >
-        {this.props.objects.map(object =>
-          <Tab key={`${object.type}/${object.id}`}
+        {this.props.objects.map(object => (
+          <Tab
+            key={`${object.type}/${object.id}`}
             label={this.renderLabel(object)}
             classes={{
               root: classes.tabRoot
             }}
           />
-        )}
+        ))}
       </Tabs>
     )
   }
 
-  renderLabel(object){
+  renderLabel(object) {
     let changed = _.find(this.props.changedObjects, object) ? '*' : ''
     let label = null
 
-    switch(object.type){
+    switch (object.type) {
       case objectTypes.SEARCH:
         label = 'search: ' + object.id
         break
@@ -69,18 +69,24 @@ class ContentTabs extends React.Component {
         break
     }
 
-    return <span className={this.props.classes.tabLabel}>{label}{this.renderIcon(object)}</span>
+    return (
+      <span className={this.props.classes.tabLabel}>
+        {label}
+        {this.renderIcon(object)}
+      </span>
+    )
   }
 
-  renderIcon(object){
-    return <CloseIcon
-      onClick={(event) => this.handleTabClose(event, object)}
-      classes={{
-        root: this.props.classes.iconRoot
-      }}
-    />
+  renderIcon(object) {
+    return (
+      <CloseIcon
+        onClick={event => this.handleTabClose(event, object)}
+        classes={{
+          root: this.props.classes.iconRoot
+        }}
+      />
+    )
   }
-
 }
 
 export default withStyles(styles)(ContentTabs)
diff --git a/openbis_ng_ui/src/components/common/dnd/DragAndDropProvider.jsx b/openbis_ng_ui/src/components/common/dnd/DragAndDropProvider.jsx
index 33c3e32565b54d4334052b0cd55c318db18806a8..7adbbf9c87d0c039fbd269dcf6ed6721a79ceb02 100644
--- a/openbis_ng_ui/src/components/common/dnd/DragAndDropProvider.jsx
+++ b/openbis_ng_ui/src/components/common/dnd/DragAndDropProvider.jsx
@@ -4,7 +4,6 @@ import HTML5Backend from 'react-dnd-html5-backend'
 import logger from '../../../common/logger.js'
 
 class DragAndDropProvider extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'DragAndDropProvider.render')
 
diff --git a/openbis_ng_ui/src/components/common/error/Error.jsx b/openbis_ng_ui/src/components/common/error/Error.jsx
index 881a2777d845bc2a4a555a9f998b0bf70d7f0985..8cc2b851f1149b029e929bf3407614a56adf53a4 100644
--- a/openbis_ng_ui/src/components/common/error/Error.jsx
+++ b/openbis_ng_ui/src/components/common/error/Error.jsx
@@ -3,16 +3,17 @@ import ErrorDialog from './ErrorDialog.jsx'
 import logger from '../../../common/logger.js'
 
 class Error extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Error.render')
 
     return (
       <div>
-        {
-          this.props.error &&
-          <ErrorDialog error={this.props.error} onClose={this.props.errorClosed}/>
-        }
+        {this.props.error && (
+          <ErrorDialog
+            error={this.props.error}
+            onClose={this.props.errorClosed}
+          />
+        )}
         {this.props.children}
       </div>
     )
diff --git a/openbis_ng_ui/src/components/common/error/ErrorDialog.jsx b/openbis_ng_ui/src/components/common/error/ErrorDialog.jsx
index f8d504134de996ad6ce90ea6c47a5a0af8c2d2ae..ea24379f71fc4712582289e9e444ee31211e8a0f 100644
--- a/openbis_ng_ui/src/components/common/error/ErrorDialog.jsx
+++ b/openbis_ng_ui/src/components/common/error/ErrorDialog.jsx
@@ -13,8 +13,7 @@ import Slide from '@material-ui/core/Slide'
 import logger from '../../../common/logger.js'
 import profile from '../../../profile.js'
 
-
-const dialogStyles = (theme) => ({
+const dialogStyles = theme => ({
   paper: {
     backgroundColor: theme.palette.error.main
   }
@@ -25,31 +24,45 @@ const StyledDialog = withStyles(dialogStyles)(Dialog)
 const ANIMATION_TIME_MS = 250
 
 function Transition(props) {
-  return <Slide
-    direction="up"
-    timeout={{ enter: ANIMATION_TIME_MS, exit: ANIMATION_TIME_MS }}
-    {...props}
-  />
+  return (
+    <Slide
+      direction='up'
+      timeout={{ enter: ANIMATION_TIME_MS, exit: ANIMATION_TIME_MS }}
+      {...props}
+    />
+  )
 }
 
 class ErrorDialog extends React.Component {
-
   state = {
-    open: true,
+    open: true
   }
 
   getErrorMailtoHref() {
     let report =
-      'agent: ' + navigator.userAgent + '%0D%0A' +
-      'domain: ' + location.hostname + '%0D%0A' +
-      'timestamp: ' + new Date() + '%0D%0A' +
-      'href: ' + location.href.replace(new RegExp('&', 'g'), ' - ') + '%0D%0A' +
-      'error: ' + JSON.stringify(this.props.error['data'])
+      'agent: ' +
+      navigator.userAgent +
+      '%0D%0A' +
+      'domain: ' +
+      location.hostname +
+      '%0D%0A' +
+      'timestamp: ' +
+      new Date() +
+      '%0D%0A' +
+      'href: ' +
+      location.href.replace(new RegExp('&', 'g'), ' - ') +
+      '%0D%0A' +
+      'error: ' +
+      JSON.stringify(this.props.error['data'])
 
     let href =
-      'mailto:' + profile.devEmail +
-      '?subject=openBIS Error Report [' + location.hostname + ']' +
-      '&body=' + report
+      'mailto:' +
+      profile.devEmail +
+      '?subject=openBIS Error Report [' +
+      location.hostname +
+      ']' +
+      '&body=' +
+      report
     return href
   }
 
@@ -63,25 +76,23 @@ class ErrorDialog extends React.Component {
 
     return (
       <StyledDialog
-        open={ this.state.open }
-        onClose={ this.props.closeError }
-        scroll="paper"
-        aria-labelledby="error-dialog-title"
-        fullWidth={ true }
-        maxWidth="md"
+        open={this.state.open}
+        onClose={this.props.closeError}
+        scroll='paper'
+        aria-labelledby='error-dialog-title'
+        fullWidth={true}
+        maxWidth='md'
         TransitionComponent={Transition}
       >
-        <DialogTitle id="error-dialog-title">Error</DialogTitle>
+        <DialogTitle id='error-dialog-title'>Error</DialogTitle>
         <DialogContent>
-          <DialogContentText>
-            { this.props.error.message }
-          </DialogContentText>
+          <DialogContentText>{this.props.error.message}</DialogContentText>
         </DialogContent>
         <DialogActions>
-          <Button onClick={ this.close.bind(this) } color="primary">
+          <Button onClick={this.close.bind(this)} color='primary'>
             Dismiss
           </Button>
-          <Button color="primary" href={this.getErrorMailtoHref()}>
+          <Button color='primary' href={this.getErrorMailtoHref()}>
             Send error report
           </Button>
         </DialogActions>
@@ -91,7 +102,7 @@ class ErrorDialog extends React.Component {
 }
 
 ErrorDialog.propTypes = {
-  error: PropTypes.any.isRequired,
+  error: PropTypes.any.isRequired
 }
 
 export default ErrorDialog
diff --git a/openbis_ng_ui/src/components/common/form/EditableField.jsx b/openbis_ng_ui/src/components/common/form/EditableField.jsx
index c6959a2d8f0d3a8b06fce1d0fb1b78b992e95488..858f08fbebacc0772f0e24101fb5613def8260ec 100644
--- a/openbis_ng_ui/src/components/common/form/EditableField.jsx
+++ b/openbis_ng_ui/src/components/common/form/EditableField.jsx
@@ -1,11 +1,11 @@
 import _ from 'lodash'
 import React from 'react'
 import EditIcon from '@material-ui/icons/EditOutlined'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import * as util from '../../../common/util.js'
 import logger from '../../../common/logger.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   container: {
     display: 'inline-flex',
     margin: '1px 30px 1px 1px',
@@ -20,12 +20,11 @@ const styles = (theme) => ({
     margin: '0px',
     border: '1px solid',
     borderColor: theme.palette.action.selected
-  },
+  }
 })
 
 class EditableField extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.state = {
       edited: false,
@@ -38,32 +37,35 @@ class EditableField extends React.Component {
     this.handleBlur = this.handleBlur.bind(this)
   }
 
-  handleMouseEnter(){
+  handleMouseEnter() {
     this.setState(() => ({
       hovered: true
     }))
   }
 
-  handleMouseLeave(){
+  handleMouseLeave() {
     this.setState(() => ({
       hovered: false
     }))
   }
 
-  handleClick(event){
+  handleClick(event) {
     event.stopPropagation()
 
-    if(!this.state.edited){
-      this.setState(() => ({
-        edited: true
-      }), () => {
-        this.ref.current.focus()
-      })
+    if (!this.state.edited) {
+      this.setState(
+        () => ({
+          edited: true
+        }),
+        () => {
+          this.ref.current.focus()
+        }
+      )
     }
   }
 
-  handleBlur(){
-    if(this.state.edited){
+  handleBlur() {
+    if (this.state.edited) {
       this.setState(() => ({
         edited: false,
         hovered: false
@@ -71,24 +73,29 @@ class EditableField extends React.Component {
     }
   }
 
-  render(){
+  render() {
     logger.log(logger.DEBUG, 'EditableField.render')
 
-    const {classes} = this.props
-    const {edited, hovered} = this.state
+    const { classes } = this.props
+    const { edited, hovered } = this.state
 
-    let classNames = util.classNames(classes.container, hovered ? classes.hovered : null, edited ? classes.edited : null)
+    let classNames = util.classNames(
+      classes.container,
+      hovered ? classes.hovered : null,
+      edited ? classes.edited : null
+    )
 
-    if(edited){
+    if (edited) {
       return (
-        <span
-          className={classNames}
-          onClick={this.handleClick}
-        >
-          {this.props.renderField({edited: true, handleBlur: this.handleBlur, ref: this.ref})}
+        <span className={classNames} onClick={this.handleClick}>
+          {this.props.renderField({
+            edited: true,
+            handleBlur: this.handleBlur,
+            ref: this.ref
+          })}
         </span>
       )
-    }else{
+    } else {
       return (
         <span
           className={classNames}
@@ -96,17 +103,16 @@ class EditableField extends React.Component {
           onMouseLeave={this.handleMouseLeave}
           onClick={this.handleClick}
         >
-          {this.props.renderField({edited: false, handleBlur: this.handleBlur, ref: this.ref})}
-          {hovered &&
-            <EditIcon className={classes.icon} />
-          }
+          {this.props.renderField({
+            edited: false,
+            handleBlur: this.handleBlur,
+            ref: this.ref
+          })}
+          {hovered && <EditIcon className={classes.icon} />}
         </span>
       )
     }
   }
-
 }
 
-export default _.flow(
-  withStyles(styles),
-)(EditableField)
+export default _.flow(withStyles(styles))(EditableField)
diff --git a/openbis_ng_ui/src/components/common/form/FilterField.jsx b/openbis_ng_ui/src/components/common/form/FilterField.jsx
index c37cecaf60ad8e30358e52c7093e0c28020c49f7..02ae14d516f1da0fb23724b623de267aadbc7603 100644
--- a/openbis_ng_ui/src/components/common/form/FilterField.jsx
+++ b/openbis_ng_ui/src/components/common/form/FilterField.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import InputAdornment from '@material-ui/core/InputAdornment'
 import TextField from '@material-ui/core/TextField'
 import IconButton from '@material-ui/core/IconButton'
@@ -20,19 +20,18 @@ const styles = () => ({
 })
 
 class FilterField extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.filterRef = React.createRef()
     this.handleFilterChange = this.handleFilterChange.bind(this)
     this.handleFilterClear = this.handleFilterClear.bind(this)
   }
 
-  handleFilterChange(event){
+  handleFilterChange(event) {
     this.props.filterChange(event.target.value)
   }
 
-  handleFilterClear(event){
+  handleFilterClear(event) {
     event.preventDefault()
     this.props.filterChange('')
     this.filterRef.current.focus()
@@ -46,7 +45,7 @@ class FilterField extends React.Component {
     return (
       <TextField
         className={classes.field}
-        placeholder="Filter"
+        placeholder='Filter'
         value={this.props.filter}
         onChange={this.handleFilterChange}
         InputProps={{
@@ -56,39 +55,45 @@ class FilterField extends React.Component {
           classes: {
             input: classes.input
           }
-        }}/>
+        }}
+      />
     )
   }
 
-  renderFilterIcon(){
+  renderFilterIcon() {
     const classes = this.props.classes
     return (
-      <InputAdornment position="start" classes={{
-        root: classes.adornment
-      }}>
+      <InputAdornment
+        position='start'
+        classes={{
+          root: classes.adornment
+        }}
+      >
         <FilterIcon />
       </InputAdornment>
     )
   }
 
-  renderFilterClearIcon(){
+  renderFilterClearIcon() {
     const classes = this.props.classes
 
-    if(this.props.filter){
+    if (this.props.filter) {
       return (
-        <InputAdornment position="end" classes={{
-          root: classes.adornment
-        }}>
+        <InputAdornment
+          position='end'
+          classes={{
+            root: classes.adornment
+          }}
+        >
           <IconButton onMouseDown={this.handleFilterClear}>
             <CloseIcon />
           </IconButton>
         </InputAdornment>
       )
-    }else{
-      return (<React.Fragment></React.Fragment>)
+    } else {
+      return <React.Fragment></React.Fragment>
     }
   }
-
 }
 
 export default withStyles(styles)(FilterField)
diff --git a/openbis_ng_ui/src/components/common/grid/ColumnConfig.jsx b/openbis_ng_ui/src/components/common/grid/ColumnConfig.jsx
index 5b6e6584aa470926603e8e881f4ad6edde98c9d8..6829948807a088b433538cd5ce76fbe436ecf372 100644
--- a/openbis_ng_ui/src/components/common/grid/ColumnConfig.jsx
+++ b/openbis_ng_ui/src/components/common/grid/ColumnConfig.jsx
@@ -1,11 +1,10 @@
 import _ from 'lodash'
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import IconButton from '@material-ui/core/IconButton'
 import SettingsIcon from '@material-ui/icons/Settings'
 import Popover from '@material-ui/core/Popover'
-import FormControlLabel from '@material-ui/core/FormControlLabel'
-import Checkbox from '@material-ui/core/Checkbox'
+import ColumnConfigRow from './ColumnConfigRow.jsx'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
@@ -21,46 +20,31 @@ const styles = () => ({
 })
 
 class ColumnConfig extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.state = {
       el: null
     }
     this.handleOpen = this.handleOpen.bind(this)
     this.handleClose = this.handleClose.bind(this)
-    this.handleChange = this.handleChange.bind(this)
   }
 
-  handleOpen(event){
+  handleOpen(event) {
     this.setState({
       el: event.currentTarget
     })
   }
 
-  handleClose(){
+  handleClose() {
     this.setState({
       el: null
     })
   }
 
-  handleChange(event){
-    let columns = [...this.props.visibleColumns]
-    let column = event.target.value
-
-    if(columns.includes(column)){
-      _.pull(columns, column)
-    }else{
-      columns.push(column)
-    }
-
-    this.props.onColumnsChange(columns)
-  }
-
   render() {
     logger.log(logger.DEBUG, 'ColumnConfig.render')
 
-    const { classes, allColumns, visibleColumns } = this.props
+    const { classes, columns, onVisibleChange, onOrderChange } = this.props
     const { el } = this.state
 
     return (
@@ -74,25 +58,20 @@ class ColumnConfig extends React.Component {
           onClose={this.handleClose}
           anchorOrigin={{
             vertical: 'top',
-            horizontal: 'center',
+            horizontal: 'center'
           }}
           transformOrigin={{
             vertical: 'bottom',
-            horizontal: 'center',
+            horizontal: 'center'
           }}
         >
           <ol className={classes.columns}>
-            {allColumns.map(column => (
+            {columns.map(column => (
               <li key={column.field}>
-                <FormControlLabel
-                  control={
-                    <Checkbox
-                      value={column.field}
-                      checked={visibleColumns.includes(column.field)}
-                      onChange={this.handleChange}
-                    />
-                  }
-                  label={column.label || column.field}
+                <ColumnConfigRow
+                  column={column}
+                  onVisibleChange={onVisibleChange}
+                  onOrderChange={onOrderChange}
                 />
               </li>
             ))}
@@ -101,9 +80,6 @@ class ColumnConfig extends React.Component {
       </div>
     )
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(ColumnConfig)
+export default _.flow(withStyles(styles))(ColumnConfig)
diff --git a/openbis_ng_ui/src/components/common/grid/ColumnConfigRow.jsx b/openbis_ng_ui/src/components/common/grid/ColumnConfigRow.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0ff58ae3755fd4835bd99b91356628fc72c6da03
--- /dev/null
+++ b/openbis_ng_ui/src/components/common/grid/ColumnConfigRow.jsx
@@ -0,0 +1,111 @@
+import _ from 'lodash'
+import React from 'react'
+import { DragSource } from 'react-dnd'
+import { DropTarget } from 'react-dnd'
+import FormControlLabel from '@material-ui/core/FormControlLabel'
+import Checkbox from '@material-ui/core/Checkbox'
+import DragHandleIcon from '@material-ui/icons/DragHandle'
+import RootRef from '@material-ui/core/RootRef'
+import { withStyles } from '@material-ui/core/styles'
+import logger from '../../../common/logger.js'
+
+const styles = () => ({
+  row: {
+    display: 'flex',
+    alignItems: 'center'
+  },
+  label: {
+    marginLeft: 0
+  },
+  drag: {
+    display: 'flex',
+    cursor: 'grab'
+  }
+})
+
+const source = {
+  beginDrag(props) {
+    return { column: props.column.field }
+  },
+  endDrag(props, monitor) {
+    if (monitor.getItem() && monitor.getDropResult()) {
+      const { column: sourceColumn } = monitor.getItem()
+      const { column: targetColumn } = monitor.getDropResult()
+      props.onOrderChange(sourceColumn, targetColumn)
+    }
+  }
+}
+
+function sourceCollect(connect, monitor) {
+  return {
+    connectDragSource: connect.dragSource(),
+    connectDragPreview: connect.dragPreview(),
+    isDragging: monitor.isDragging()
+  }
+}
+
+const target = {
+  drop(props) {
+    return { column: props.column.field }
+  }
+}
+
+function targetCollect(connect, monitor) {
+  return {
+    connectDropTarget: connect.dropTarget(),
+    isDragging: monitor.getItem() !== null
+  }
+}
+
+class ColumnConfigRow extends React.Component {
+  constructor(props) {
+    super(props)
+    this.handleRef = React.createRef()
+    this.rowRef = React.createRef()
+    this.handleVisibleChange = this.handleVisibleChange.bind(this)
+  }
+
+  componentDidMount() {
+    this.props.connectDragSource(this.handleRef.current)
+    this.props.connectDragPreview(this.rowRef.current)
+    this.props.connectDropTarget(this.rowRef.current)
+  }
+
+  handleVisibleChange() {
+    this.props.onVisibleChange(this.props.column.field)
+  }
+
+  render() {
+    logger.log(logger.DEBUG, 'ColumnConfigRow.render')
+
+    const { classes, column } = this.props
+
+    return (
+      <RootRef rootRef={this.rowRef}>
+        <div className={classes.row}>
+          <RootRef rootRef={this.handleRef}>
+            <div className={classes.drag}>
+              <DragHandleIcon />
+            </div>
+          </RootRef>
+          <FormControlLabel
+            classes={{ root: classes.label }}
+            control={
+              <Checkbox
+                checked={column.visible}
+                onChange={this.handleVisibleChange}
+              />
+            }
+            label={column.label || column.field}
+          />
+        </div>
+      </RootRef>
+    )
+  }
+}
+
+export default _.flow(
+  DragSource('column', source, sourceCollect),
+  DropTarget('column', target, targetCollect),
+  withStyles(styles)
+)(ColumnConfigRow)
diff --git a/openbis_ng_ui/src/components/common/grid/Grid.jsx b/openbis_ng_ui/src/components/common/grid/Grid.jsx
index dc9f18dea94ce3c49e4c9096c2a6f3ec6f1bd34d..2ad7ca827ed744c98c0d4168c2c6c2efd914b480 100644
--- a/openbis_ng_ui/src/components/common/grid/Grid.jsx
+++ b/openbis_ng_ui/src/components/common/grid/Grid.jsx
@@ -1,7 +1,7 @@
 import _ from 'lodash'
 import React from 'react'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import Table from '@material-ui/core/Table'
 import TableBody from '@material-ui/core/TableBody'
 import TableCell from '@material-ui/core/TableCell'
@@ -13,20 +13,15 @@ import ColumnConfig from '../../common/grid/ColumnConfig.jsx'
 import PageConfig from '../../common/grid/PageConfig.jsx'
 import * as ids from '../../../common/consts/ids.js'
 import * as selectors from '../../../store/selectors/selectors.js'
-import {facade, dto} from '../../../services/openbis.js'
+import { facade, dto } from '../../../services/openbis.js'
 import logger from '../../../common/logger.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   container: {
     display: 'flex',
     flexDirection: 'column',
     height: '100%'
   },
-  headerContainer: {
-    flexGrow: 0,
-    padding: theme.spacing(2),
-    paddingBottom: 0
-  },
   footerContainer: {
     display: 'flex',
     alignItems: 'center',
@@ -60,47 +55,47 @@ const styles = (theme) => ({
   }
 })
 
-function mapStateToProps(state){
+function mapStateToProps(state) {
   return {
     session: selectors.getSession(state)
   }
 }
 
 class Grid extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
 
     const sortDefault = _.isFunction(props.data) ? false : true
 
-    this.columnsArray = props.columns.map(column => ({
+    let columns = props.columns.map(column => ({
       ...column,
       label: column.label || _.upperFirst(column.field),
       render: column.render || (row => _.get(row, column.field)),
-      sort: column.sort === undefined ? sortDefault : Boolean(column.sort)
+      sort: column.sort === undefined ? sortDefault : Boolean(column.sort),
+      visible: true
     }))
-    this.columnsMap = _.keyBy(props.columns, 'field')
 
     this.state = {
       loaded: false,
-      filter: this.props.filter || '',
+      filters: this.props.filters || {},
       page: 0,
       pageSize: 10,
-      visibleColumns: Object.keys(this.columnsMap),
+      columns: columns,
       columnConfigEl: null
     }
 
     this.handleFilterChange = this.handleFilterChange.bind(this)
-    this.handleColumnsChange = this.handleColumnsChange.bind(this)
+    this.handleColumnVisibleChange = this.handleColumnVisibleChange.bind(this)
+    this.handleColumnOrderChange = this.handleColumnOrderChange.bind(this)
     this.handlePageChange = this.handlePageChange.bind(this)
     this.handlePageSizeChange = this.handlePageSizeChange.bind(this)
   }
 
-  componentDidMount(){
+  componentDidMount() {
     this.load()
   }
 
-  load(){
+  load() {
     this.loadSettings().then(() => {
       this.loadData().then(() => {
         this.setState(() => ({
@@ -110,10 +105,10 @@ class Grid extends React.Component {
     })
   }
 
-  loadData(){
-    if(_.isFunction(this.props.data)){
+  loadData() {
+    if (_.isFunction(this.props.data)) {
       let loadConfig = {
-        filter: this.state.filter,
+        filters: this.state.filters,
         page: this.state.page,
         pageSize: this.state.pageSize,
         sort: this.state.sort,
@@ -125,7 +120,7 @@ class Grid extends React.Component {
           totalCount
         }))
       })
-    }else if(!this.state.loaded){
+    } else if (!this.state.loaded) {
       this.setState(() => ({
         objects: this.props.data
       }))
@@ -133,7 +128,7 @@ class Grid extends React.Component {
     return Promise.resolve()
   }
 
-  loadSettings(){
+  loadSettings() {
     let id = new dto.PersonPermId(this.props.session.userName)
     let fo = new dto.PersonFetchOptions()
     fo.withWebAppSettings(ids.WEB_APP_ID).withAllSettings()
@@ -141,13 +136,31 @@ class Grid extends React.Component {
     return facade.getPersons([id], fo).then(map => {
       let person = map[id]
       let webAppSettings = person.webAppSettings[ids.WEB_APP_ID]
-      if(webAppSettings && webAppSettings.settings){
+      if (webAppSettings && webAppSettings.settings) {
         let gridSettings = webAppSettings.settings[this.props.id]
-        if(gridSettings){
+        if (gridSettings) {
           let settings = JSON.parse(gridSettings.value)
-          if(settings){
+          if (settings) {
+            let newColumns = [...this.state.columns]
+            newColumns.sort((c1, c2) => {
+              let index1 = _.findIndex(settings.columns, ['field', c1.field])
+              let index2 = _.findIndex(settings.columns, ['field', c2.field])
+              return index1 - index2
+            })
+            newColumns = newColumns.map(column => {
+              let setting = _.find(settings.columns, ['field', column.field])
+              if (setting) {
+                return {
+                  ...column,
+                  visible: setting.visible
+                }
+              } else {
+                return column
+              }
+            })
             this.setState(() => ({
-              ...settings
+              ...settings,
+              columns: newColumns
             }))
           }
         }
@@ -155,12 +168,17 @@ class Grid extends React.Component {
     })
   }
 
-  saveSettings(){
+  saveSettings() {
+    let columns = this.state.columns.map(column => ({
+      field: column.field,
+      visible: column.visible
+    }))
+
     let settings = {
       pageSize: this.state.pageSize,
       sort: this.state.sort,
       sortDirection: this.state.sortDirection,
-      visibleColumns: this.state.visibleColumns
+      columns
     }
 
     let gridSettings = new dto.WebAppSettingCreation()
@@ -174,114 +192,174 @@ class Grid extends React.Component {
     facade.updatePersons([update])
   }
 
-  handleFilterChange(filter){
-    this.setState(() => ({
-      page: 0,
-      filter
-    }), () => {
-      this.loadData()
-    })
+  handleFilterChange(column, filter) {
+    let filters = {
+      ...this.state.filters,
+      [column]: filter
+    }
+
+    this.setState(
+      () => ({
+        page: 0,
+        filters
+      }),
+      () => {
+        this.loadData()
+      }
+    )
   }
 
-  handleColumnsChange(visibleColumns){
-    this.setState(() => ({
-      visibleColumns
-    }), () => {
-      this.saveSettings()
+  handleColumnVisibleChange(field) {
+    let columns = this.state.columns.map(column => {
+      if (column.field === field) {
+        return {
+          ...column,
+          visible: !column.visible
+        }
+      } else {
+        return column
+      }
     })
+
+    this.setState(
+      () => ({
+        columns
+      }),
+      () => {
+        this.saveSettings()
+      }
+    )
   }
 
-  handleSortChange(column){
-    if(!column.sort){
+  handleColumnOrderChange(sourceField, targetField) {
+    let columns = [...this.state.columns]
+    let sourceIndex = _.findIndex(columns, ['field', sourceField])
+    let targetIndex = _.findIndex(columns, ['field', targetField])
+
+    if (sourceIndex !== -1 && targetIndex !== -1) {
+      let temp = columns[sourceIndex]
+      columns[sourceIndex] = columns[targetIndex]
+      columns[targetIndex] = temp
+    }
+
+    this.setState(
+      () => ({
+        columns
+      }),
+      () => {
+        this.saveSettings()
+      }
+    )
+  }
+
+  handleSortChange(column) {
+    if (!column.sort) {
       return
     }
     return () => {
-      this.setState((prevState) => ({
-        sort: column.field,
-        sortDirection: prevState.sortDirection === 'asc' ? 'desc' : 'asc'
-      }), () => {
-        this.saveSettings()
-        this.loadData()
-      })
+      this.setState(
+        prevState => ({
+          sort: column.field,
+          sortDirection: prevState.sortDirection === 'asc' ? 'desc' : 'asc'
+        }),
+        () => {
+          this.saveSettings()
+          this.loadData()
+        }
+      )
     }
   }
 
-  handlePageChange(page){
-    this.setState(() => ({
-      page
-    }), () => {
-      this.loadData()
-    })
+  handlePageChange(page) {
+    this.setState(
+      () => ({
+        page
+      }),
+      () => {
+        this.loadData()
+      }
+    )
   }
 
-  handlePageSizeChange(pageSize){
-    this.setState(() => ({
-      page: 0,
-      pageSize
-    }), () => {
-      this.saveSettings()
-      this.loadData()
-    })
+  handlePageSizeChange(pageSize) {
+    this.setState(
+      () => ({
+        page: 0,
+        pageSize
+      }),
+      () => {
+        this.saveSettings()
+        this.loadData()
+      }
+    )
   }
 
-  filter(objects){
-    const filter = this.state.filter ? this.state.filter.trim().toUpperCase() : null
-
-    function matches(value){
-      if(filter){
-        return value ? value.trim().toUpperCase().includes(filter) : false
-      }else{
+  filter(objects) {
+    function matches(value, filter) {
+      if (filter) {
+        return value
+          ? value
+              .trim()
+              .toUpperCase()
+              .includes(filter.trim().toUpperCase())
+          : false
+      } else {
         return true
       }
     }
 
     return _.filter(objects, row => {
-      return this.state.visibleColumns.some(visibleColumn => {
-        let column = this.columnsMap[visibleColumn]
+      let matchesAll = true
+      this.state.columns.forEach(column => {
         let value = _.get(row, column.field)
-        return matches(value)
+        let filter = this.state.filters[column.field]
+        matchesAll = matchesAll && matches(value, filter)
       })
+      return matchesAll
     })
   }
 
-  sort(objects){
+  sort(objects) {
     const { sort, sortDirection } = this.state
 
-    if(sort){
-      return objects.sort((t1, t2)=>{
+    if (sort) {
+      let column = _.find(this.state.columns, ['field', sort])
+      return objects.sort((t1, t2) => {
         let sign = sortDirection === 'asc' ? 1 : -1
-        let column = this.columnsMap[sort]
         let v1 = _.get(t1, column.field) || ''
         let v2 = _.get(t2, column.field) || ''
         return sign * v1.localeCompare(v2)
       })
-    }else{
+    } else {
       return objects
     }
   }
 
-  page(objects){
+  page(objects) {
     const { page, pageSize } = this.state
-    return objects.slice(page*pageSize, Math.min(objects.length, (page+1)*pageSize))
+    return objects.slice(
+      page * pageSize,
+      Math.min(objects.length, (page + 1) * pageSize)
+    )
   }
 
   render() {
     logger.log(logger.DEBUG, 'Grid.render')
 
-    if(!this.state.loaded){
+    if (!this.state.loaded) {
       return <React.Fragment />
     }
 
     const { classes } = this.props
-    const { page, pageSize, filter, visibleColumns } = this.state
+    const { page, pageSize, columns } = this.state
 
     let pagedObjects = null
     let totalCount = null
 
-    if(_.isFunction(this.props.data)){
+    if (_.isFunction(this.props.data)) {
       pagedObjects = this.state.objects
       totalCount = this.state.totalCount
-    }else{
+    } else {
       const filteredObjects = this.filter([...this.state.objects])
       const sortedObjects = this.sort(filteredObjects)
       pagedObjects = this.page(sortedObjects)
@@ -290,28 +368,21 @@ class Grid extends React.Component {
 
     return (
       <div className={classes.container}>
-        <div className={classes.headerContainer}>
-          <FilterField
-            filter={filter}
-            filterChange={this.handleFilterChange}
-          />
-        </div>
         <div className={classes.tableContainer}>
           <Table classes={{ root: classes.table }}>
             <TableHead classes={{ root: classes.tableHeader }}>
               <TableRow>
-                {this.columnsArray.map(column =>
-                  this.renderHeaderCell(column)
-                )}
+                {columns.map(column => this.renderFilterCell(column))}
+              </TableRow>
+              <TableRow>
+                {columns.map(column => this.renderHeaderCell(column))}
               </TableRow>
             </TableHead>
             <TableBody>
               {pagedObjects.map(row => {
                 return (
                   <TableRow key={row.id} hover>
-                    {this.columnsArray.map(column =>
-                      this.renderRowCell(column, row)
-                    )}
+                    {columns.map(column => this.renderRowCell(column, row))}
                   </TableRow>
                 )
               })}
@@ -330,20 +401,20 @@ class Grid extends React.Component {
             onPageSizeChange={this.handlePageSizeChange}
           />
           <ColumnConfig
-            allColumns={this.columnsArray}
-            visibleColumns={visibleColumns}
-            onColumnsChange={this.handleColumnsChange}
+            columns={columns}
+            onVisibleChange={this.handleColumnVisibleChange}
+            onOrderChange={this.handleColumnOrderChange}
           />
         </div>
       </div>
     )
   }
 
-  renderHeaderCell(column){
-    const { visibleColumns, sort, sortDirection } = this.state
+  renderHeaderCell(column) {
+    const { sort, sortDirection } = this.state
 
-    if(visibleColumns.includes(column.field)){
-      if(column.sort){
+    if (column.visible) {
+      if (column.sort) {
         return (
           <TableCell key={column.field}>
             <TableSortLabel
@@ -355,36 +426,50 @@ class Grid extends React.Component {
             </TableSortLabel>
           </TableCell>
         )
-      }else{
-        return (
-          <TableCell key={column.field}>
-            {column.label}
-          </TableCell>
-        )
+      } else {
+        return <TableCell key={column.field}>{column.label}</TableCell>
       }
-    }else{
+    } else {
       return null
     }
   }
 
-  renderRowCell(column, row){
-    const { visibleColumns } = this.state
+  renderFilterCell(column) {
+    const { filters } = this.state
 
-    if(visibleColumns.includes(column.field)){
-      let rendered = column.render(row)
+    if (column.visible) {
+      let filter = filters[column.field] || ''
+      let filterChange = filter => {
+        this.handleFilterChange(column.field, filter)
+      }
       return (
         <TableCell key={column.field}>
-          {rendered ? rendered : <span>&nbsp;</span> }
+          <FilterField filter={filter} filterChange={filterChange} />
         </TableCell>
       )
-    }else{
+    } else {
       return null
     }
   }
 
+  renderRowCell(column, row) {
+    if (column.visible) {
+      let rendered = column.render(row)
+      return (
+        <TableCell key={column.field}>
+          {rendered ? rendered : <span>&nbsp;</span>}
+        </TableCell>
+      )
+    } else {
+      return null
+    }
+  }
 }
 
 export default _.flow(
-  connect(mapStateToProps, null),
+  connect(
+    mapStateToProps,
+    null
+  ),
   withStyles(styles)
 )(Grid)
diff --git a/openbis_ng_ui/src/components/common/grid/PageConfig.jsx b/openbis_ng_ui/src/components/common/grid/PageConfig.jsx
index bc1ff16bceac85e4465a96f4db3ff623cabdd6a2..9dedc1e8f18abdfbf47ce82c271b89c6fb2bab96 100644
--- a/openbis_ng_ui/src/components/common/grid/PageConfig.jsx
+++ b/openbis_ng_ui/src/components/common/grid/PageConfig.jsx
@@ -1,6 +1,6 @@
 import _ from 'lodash'
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import Typography from '@material-ui/core/Typography'
 import TextField from '@material-ui/core/TextField'
 import FormControlLabel from '@material-ui/core/FormControlLabel'
@@ -33,8 +33,7 @@ const styles = () => ({
 })
 
 class PageConfig extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.handlePageSizeChange = this.handlePageSizeChange.bind(this)
     this.handleFirstPageButtonClick = this.handleFirstPageButtonClick.bind(this)
@@ -43,7 +42,7 @@ class PageConfig extends React.Component {
     this.handleLastPageButtonClick = this.handleLastPageButtonClick.bind(this)
   }
 
-  handlePageSizeChange(event){
+  handlePageSizeChange(event) {
     this.props.onPageSizeChange(event.target.value)
   }
 
@@ -60,13 +59,15 @@ class PageConfig extends React.Component {
   }
 
   handleLastPageButtonClick() {
-    this.props.onPageChange(Math.max(0, Math.ceil(this.props.count / this.props.pageSize) - 1))
+    this.props.onPageChange(
+      Math.max(0, Math.ceil(this.props.count / this.props.pageSize) - 1)
+    )
   }
 
   render() {
     logger.log(logger.DEBUG, 'PageConfig.render')
 
-    const { classes, count, page, pageSize} = this.props
+    const { classes, count, page, pageSize } = this.props
 
     return (
       <div className={classes.container}>
@@ -82,7 +83,9 @@ class PageConfig extends React.Component {
                 onChange={this.handlePageSizeChange}
               >
                 {[5, 10, 20, 50, 100].map(pageSize => (
-                  <option key={pageSize} value={pageSize}>{pageSize}</option>
+                  <option key={pageSize} value={pageSize}>
+                    {pageSize}
+                  </option>
                 ))}
               </TextField>
             }
@@ -90,37 +93,42 @@ class PageConfig extends React.Component {
               label: classes.pageSizeLabel,
               labelPlacementStart: classes.pageSizeLabelPlacement
             }}
-            label="Rows per page: "
-            labelPlacement="start"
+            label='Rows per page: '
+            labelPlacement='start'
           />
         </div>
         <div className={classes.pageRange}>
           <Typography>
-            {Math.min(count, page * pageSize + 1)}-{Math.min(count, (page + 1) * pageSize)} of {count}
+            {Math.min(count, page * pageSize + 1)}-
+            {Math.min(count, (page + 1) * pageSize)} of {count}
           </Typography>
         </div>
         <div className={classes.pageButtons}>
           <IconButton
             onClick={this.handleFirstPageButtonClick}
             disabled={page === 0}
-            aria-label="First Page"
+            aria-label='First Page'
           >
             <FirstPageIcon />
           </IconButton>
-          <IconButton onClick={this.handleBackButtonClick} disabled={page === 0} aria-label="Previous Page">
+          <IconButton
+            onClick={this.handleBackButtonClick}
+            disabled={page === 0}
+            aria-label='Previous Page'
+          >
             <KeyboardArrowLeft />
           </IconButton>
           <IconButton
             onClick={this.handleNextButtonClick}
             disabled={page >= Math.ceil(count / pageSize) - 1}
-            aria-label="Next Page"
+            aria-label='Next Page'
           >
             <KeyboardArrowRight />
           </IconButton>
           <IconButton
             onClick={this.handleLastPageButtonClick}
             disabled={page >= Math.ceil(count / pageSize) - 1}
-            aria-label="Last Page"
+            aria-label='Last Page'
           >
             <LastPageIcon />
           </IconButton>
@@ -128,9 +136,6 @@ class PageConfig extends React.Component {
       </div>
     )
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(PageConfig)
+export default _.flow(withStyles(styles))(PageConfig)
diff --git a/openbis_ng_ui/src/components/common/loading/Loading.jsx b/openbis_ng_ui/src/components/common/loading/Loading.jsx
index 17951d7dbabfd8857a0bb3b42f9fb2a0ddee44d7..fb9c8214f024a020826f5236f226de160cd0e2cf 100644
--- a/openbis_ng_ui/src/components/common/loading/Loading.jsx
+++ b/openbis_ng_ui/src/components/common/loading/Loading.jsx
@@ -1,9 +1,9 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import CircularProgress from '@material-ui/core/CircularProgress'
 import logger from '../../../common/logger.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   loader: {
     position: 'absolute',
     width: '100%',
@@ -11,7 +11,7 @@ const styles = (theme) => ({
     zIndex: 1000,
     backgroundColor: theme.palette.background.paper,
     opacity: 0.8,
-    textAlign: 'center',
+    textAlign: 'center'
   },
   progress: {
     position: 'absolute',
@@ -21,7 +21,6 @@ const styles = (theme) => ({
 })
 
 class Loading extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Loading.render')
 
@@ -29,12 +28,11 @@ class Loading extends React.Component {
 
     return (
       <div>
-        {
-          this.props.loading &&
+        {this.props.loading && (
           <div className={classes.loader}>
-            <CircularProgress className={classes.progress}/>
+            <CircularProgress className={classes.progress} />
           </div>
-        }
+        )}
         {this.props.children}
       </div>
     )
diff --git a/openbis_ng_ui/src/components/common/menu/Menu.jsx b/openbis_ng_ui/src/components/common/menu/Menu.jsx
index ff4b7684e227720e25a0a73bd16920bb12c50d59..acbf147e7d82f001100394afd1809cc2a6c3eaf0 100644
--- a/openbis_ng_ui/src/components/common/menu/Menu.jsx
+++ b/openbis_ng_ui/src/components/common/menu/Menu.jsx
@@ -10,22 +10,22 @@ import SearchIcon from '@material-ui/icons/Search'
 import CloseIcon from '@material-ui/icons/Close'
 import LogoutIcon from '@material-ui/icons/PowerSettingsNew'
 import { fade } from '@material-ui/core/styles/colorManipulator'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 import * as actions from '../../../store/actions/actions.js'
 import * as selectors from '../../../store/selectors/selectors.js'
 import * as pages from '../../../common/consts/pages.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   tabs: {
-    flexGrow: 1,
+    flexGrow: 1
   },
   search: {
     color: theme.palette.background.paper,
     backgroundColor: fade(theme.palette.background.paper, 0.15),
     '&:hover': {
-      backgroundColor: fade(theme.palette.background.paper, 0.25),
+      backgroundColor: fade(theme.palette.background.paper, 0.25)
     },
     borderRadius: theme.shape.borderRadius,
     paddingLeft: theme.spacing(1),
@@ -34,8 +34,8 @@ const styles = (theme) => ({
     transition: theme.transitions.create('width'),
     width: '200px',
     '&:focus-within': {
-      width: '300px',
-    },
+      width: '300px'
+    }
   },
   searchIcon: {
     paddingLeft: theme.spacing(1) / 2,
@@ -45,28 +45,27 @@ const styles = (theme) => ({
   searchClear: {
     cursor: 'pointer'
   }
-
 })
 
-function mapStateToProps(state){
+function mapStateToProps(state) {
   return {
     currentPage: selectors.getCurrentPage(state),
     searchText: selectors.getSearch(state)
   }
 }
 
-function mapDispatchToProps(dispatch, ownProps){
+function mapDispatchToProps(dispatch, ownProps) {
   return {
-    currentPageChange: (event, value) => dispatch(actions.currentPageChange(value)),
-    searchChange: (value) => dispatch(actions.searchChange(value)),
-    search: (value) => dispatch(actions.search(ownProps.page, value)),
+    currentPageChange: (event, value) =>
+      dispatch(actions.currentPageChange(value)),
+    searchChange: value => dispatch(actions.searchChange(value)),
+    search: value => dispatch(actions.search(ownProps.page, value)),
     logout: () => dispatch(actions.logout())
   }
 }
 
 class Menu extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.searchRef = React.createRef()
     this.handleSearchChange = this.handleSearchChange.bind(this)
@@ -74,17 +73,17 @@ class Menu extends React.Component {
     this.handleSearchClear = this.handleSearchClear.bind(this)
   }
 
-  handleSearchChange(event){
+  handleSearchChange(event) {
     this.props.searchChange(event.target.value)
   }
 
-  handleSearchKeyPress(event){
-    if(event.key === 'Enter'){
+  handleSearchKeyPress(event) {
+    if (event.key === 'Enter') {
       this.props.search(this.props.searchText)
     }
   }
 
-  handleSearchClear(event){
+  handleSearchClear(event) {
     event.preventDefault()
     this.props.searchChange('')
     this.searchRef.current.focus()
@@ -93,19 +92,21 @@ class Menu extends React.Component {
   render() {
     logger.log(logger.DEBUG, 'Menu.render')
 
-    const {classes, searchText} = this.props
+    const { classes, searchText } = this.props
 
     return (
-      <AppBar position="static">
+      <AppBar position='static'>
         <Toolbar>
-          <Tabs value={this.props.currentPage}
+          <Tabs
+            value={this.props.currentPage}
             onChange={this.props.currentPageChange}
-            classes={{root: classes.tabs}}>
-            <Tab value={pages.TYPES} label="Types"/>
-            <Tab value={pages.USERS} label="Users"/>
+            classes={{ root: classes.tabs }}
+          >
+            <Tab value={pages.TYPES} label='Types' />
+            <Tab value={pages.USERS} label='Users' />
           </Tabs>
           <TextField
-            placeholder="Search..."
+            placeholder='Search...'
             value={searchText}
             onChange={this.handleSearchChange}
             onKeyPress={this.handleSearchKeyPress}
@@ -115,22 +116,24 @@ class Menu extends React.Component {
               startAdornment: this.renderSearchIcon(),
               endAdornment: this.renderSearchClearIcon(),
               classes: {
-                root: classes.search,
+                root: classes.search
               }
-            }}/>
+            }}
+          />
           <Button
-            variant="contained"
-            color="primary"
-            onClick={this.props.logout}>
-            <LogoutIcon/>
+            variant='contained'
+            color='primary'
+            onClick={this.props.logout}
+          >
+            <LogoutIcon />
           </Button>
         </Toolbar>
       </AppBar>
     )
   }
 
-  renderSearchIcon(){
-    const {classes} = this.props
+  renderSearchIcon() {
+    const { classes } = this.props
     return (
       <InputAdornment>
         <SearchIcon classes={{ root: classes.searchIcon }} />
@@ -138,9 +141,9 @@ class Menu extends React.Component {
     )
   }
 
-  renderSearchClearIcon(){
-    const {classes, searchText} = this.props
-    if(searchText){
+  renderSearchClearIcon() {
+    const { classes, searchText } = this.props
+    if (searchText) {
       return (
         <InputAdornment>
           <CloseIcon
@@ -149,11 +152,13 @@ class Menu extends React.Component {
           />
         </InputAdornment>
       )
-    }else{
-      return (<React.Fragment></React.Fragment>)
+    } else {
+      return <React.Fragment></React.Fragment>
     }
   }
-
 }
 
-export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Menu))
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(withStyles(styles)(Menu))
diff --git a/openbis_ng_ui/src/components/login/Login.jsx b/openbis_ng_ui/src/components/login/Login.jsx
index b9211c38390b432fd83cc3248721244b6bb54898..87ca2fa6b0aaeb9db18f7650941dcf59c47271e4 100644
--- a/openbis_ng_ui/src/components/login/Login.jsx
+++ b/openbis_ng_ui/src/components/login/Login.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
-import {connect} from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
+import { connect } from 'react-redux'
 import flow from 'lodash/flow'
 
 import Button from '@material-ui/core/Button'
@@ -17,39 +17,37 @@ const styles = {
     marginTop: '10%',
     marginBottom: '10em',
     width: '30em',
-    margin: '0 auto',
+    margin: '0 auto'
   },
   textField: {
-    width: '100%',
+    width: '100%'
   },
   button: {
-    marginTop: '1em',
+    marginTop: '1em'
   },
   container: {
     width: '100%',
     height: '100%',
-    overflow: 'auto',
-  },
+    overflow: 'auto'
+  }
 }
 
 function mapStateToProps() {
-  return {
-  }
+  return {}
 }
 
 function mapDispatchToProps(dispatch) {
   return {
-    login: (user, password) => dispatch(actions.login(user, password)),
+    login: (user, password) => dispatch(actions.login(user, password))
   }
 }
 
 class WithLogin extends React.Component {
-
   state = {}
 
   handleChange = name => event => {
     this.setState({
-      [name]: event.target.value,
+      [name]: event.target.value
     })
   }
 
@@ -74,41 +72,39 @@ class WithLogin extends React.Component {
           <form>
             <Card className={classes.card}>
               <CardContent>
-                <Typography variant="h6">
-                  Login
-                </Typography>
+                <Typography variant='h6'>Login</Typography>
                 <TextField
-                  id="standard-name"
-                  label="User"
+                  id='standard-name'
+                  label='User'
                   className={classes.textField}
-                  margin="normal"
-                  autoComplete="username"
+                  margin='normal'
+                  autoComplete='username'
                   autoFocus={true}
-                  onKeyPress={(e) => {
+                  onKeyPress={e => {
                     this.keyPress(e)
                   }}
                   onChange={this.handleChange('user')}
                 />
                 <TextField
-                  id="standard-password-input"
-                  label="Password"
+                  id='standard-password-input'
+                  label='Password'
                   className={classes.textField}
-                  type="password"
-                  autoComplete="current-password"
-                  margin="normal"
-                  onKeyPress={(e) => {
+                  type='password'
+                  autoComplete='current-password'
+                  margin='normal'
+                  onKeyPress={e => {
                     this.keyPress(e)
                   }}
                   onChange={this.handleChange('password')}
                 />
                 <Button
                   onClick={this.login}
-                  color="primary"
+                  color='primary'
                   className={classes.button}
-                  variant="contained">
+                  variant='contained'
+                >
                   Login
                 </Button>
-
               </CardContent>
             </Card>
           </form>
@@ -119,6 +115,9 @@ class WithLogin extends React.Component {
 }
 
 export default flow(
-  connect(mapStateToProps, mapDispatchToProps),
+  connect(
+    mapStateToProps,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(WithLogin)
diff --git a/openbis_ng_ui/src/components/types/Types.jsx b/openbis_ng_ui/src/components/types/Types.jsx
index 3c694179b2a9f5e4b59b19157208fa8c8248a9c8..ebd8f3d308988810cf2bea7ca5b94b555e2b8f08 100644
--- a/openbis_ng_ui/src/components/types/Types.jsx
+++ b/openbis_ng_ui/src/components/types/Types.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../common/logger.js'
 import * as pages from '../../common/consts/pages.js'
 import * as objectType from '../../common/consts/objectType.js'
@@ -17,7 +17,7 @@ const styles = () => ({
   container: {
     display: 'flex',
     width: '100%'
-  },
+  }
 })
 
 const objectTypeToComponent = {
@@ -29,7 +29,6 @@ const objectTypeToComponent = {
 }
 
 class Types extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Types.render')
 
@@ -37,12 +36,14 @@ class Types extends React.Component {
 
     return (
       <div className={classes.container}>
-        <Browser page={pages.TYPES}/>
-        <Content page={pages.TYPES} objectTypeToComponent={objectTypeToComponent}/>
+        <Browser page={pages.TYPES} />
+        <Content
+          page={pages.TYPES}
+          objectTypeToComponent={objectTypeToComponent}
+        />
       </div>
     )
   }
-
 }
 
 export default withStyles(styles)(Types)
diff --git a/openbis_ng_ui/src/components/types/collectionType/CollectionType.jsx b/openbis_ng_ui/src/components/types/collectionType/CollectionType.jsx
index c15e8e68333d6f5fdf3453475bc273eccbad4025..cd7c770d82f7e0459f7ef88baf66b24f95202680 100644
--- a/openbis_ng_ui/src/components/types/collectionType/CollectionType.jsx
+++ b/openbis_ng_ui/src/components/types/collectionType/CollectionType.jsx
@@ -2,12 +2,10 @@ import React from 'react'
 import logger from '../../../common/logger.js'
 
 class CollectionType extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'CollectionType.render')
     return <div>CollectionType</div>
   }
-
 }
 
 export default CollectionType
diff --git a/openbis_ng_ui/src/components/types/dataSetType/DataSetType.jsx b/openbis_ng_ui/src/components/types/dataSetType/DataSetType.jsx
index ee4f5aa81ff8b56c688f64544956b0cc112de281..ca7fb3f06693bbcb3a1dcdce618fc2f34586fd68 100644
--- a/openbis_ng_ui/src/components/types/dataSetType/DataSetType.jsx
+++ b/openbis_ng_ui/src/components/types/dataSetType/DataSetType.jsx
@@ -2,12 +2,10 @@ import React from 'react'
 import logger from '../../../common/logger.js'
 
 class DataSetType extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'DataSetType.render')
     return <div>DataSetType</div>
   }
-
 }
 
 export default DataSetType
diff --git a/openbis_ng_ui/src/components/types/materialType/MaterialType.jsx b/openbis_ng_ui/src/components/types/materialType/MaterialType.jsx
index 8493629f2cf2ebb658724681d7fd96344c9added..34a6831ea5bd70ec2072d913693f837e0279c6b3 100644
--- a/openbis_ng_ui/src/components/types/materialType/MaterialType.jsx
+++ b/openbis_ng_ui/src/components/types/materialType/MaterialType.jsx
@@ -2,12 +2,10 @@ import React from 'react'
 import logger from '../../../common/logger.js'
 
 class MaterialType extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'MaterialType.render')
     return <div>MaterialType</div>
   }
-
 }
 
 export default MaterialType
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectType.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectType.jsx
index 96d78d7f7eb672da9d1dedf8faeb2eb5106ac117..934eef36cccf271a187a64762cd9475433db1730 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectType.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectType.jsx
@@ -1,16 +1,16 @@
 import _ from 'lodash'
 import React from 'react'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import ObjectTypeForm from './ObjectTypeForm.jsx'
 import ObjectTypeFooter from './ObjectTypeFooter.jsx'
 import * as pages from '../../../common/consts/pages.js'
 import * as objectTypes from '../../../common/consts/objectType.js'
 import * as actions from '../../../store/actions/actions.js'
 import logger from '../../../common/logger.js'
-import {facade, dto} from '../../../services/openbis.js'
+import { facade, dto } from '../../../services/openbis.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   container: {
     height: '100%',
     display: 'flex',
@@ -27,19 +27,29 @@ const styles = (theme) => ({
   }
 })
 
-function mapDispatchToProps(dispatch, ownProps){
+function mapDispatchToProps(dispatch, ownProps) {
   return {
-    error: (error) => { dispatch(actions.setError(error)) },
-    objectChange: (changed) => { dispatch(actions.objectChange(pages.TYPES, objectTypes.OBJECT_TYPE, ownProps.objectId, changed)) }
+    error: error => {
+      dispatch(actions.setError(error))
+    },
+    objectChange: changed => {
+      dispatch(
+        actions.objectChange(
+          pages.TYPES,
+          objectTypes.OBJECT_TYPE,
+          ownProps.objectId,
+          changed
+        )
+      )
+    }
   }
 }
 
 class ObjectType extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.state = {
-      loaded: false,
+      loaded: false
     }
     this.handleChange = this.handleChange.bind(this)
     this.handleAdd = this.handleAdd.bind(this)
@@ -49,31 +59,38 @@ class ObjectType extends React.Component {
     this.handleSave = this.handleSave.bind(this)
   }
 
-  componentDidMount(){
+  componentDidMount() {
     this.load()
   }
 
-  componentDidUpdate(){
-    if(!this.state.objectType){
+  componentDidUpdate() {
+    if (!this.state.objectType) {
       return
     }
 
-    const changed = this.state.objectType.properties.some((property) => {
-      return !property.original ||
+    const changed = this.state.objectType.properties.some(property => {
+      return (
+        !property.original ||
         !_.isEqual(property.mandatory, property.original.mandatory) ||
-        !_.isEqual(property.propertyType ? property.propertyType.code : null, property.original.propertyType ? property.original.propertyType.code : null) ||
+        !_.isEqual(
+          property.propertyType ? property.propertyType.code : null,
+          property.original.propertyType
+            ? property.original.propertyType.code
+            : null
+        ) ||
         !_.isEqual(property.ordinal, property.original.ordinal)
+      )
     })
 
-    if(changed !== this.state.changed){
-      this.setState(()=>({
+    if (changed !== this.state.changed) {
+      this.setState(() => ({
         changed
       }))
       this.props.objectChange(changed)
     }
   }
 
-  load(){
+  load() {
     this.setState({
       loaded: false
     })
@@ -90,38 +107,46 @@ class ObjectType extends React.Component {
     })
   }
 
-  loadObjectType(objectTypeId){
+  loadObjectType(objectTypeId) {
     let id = new dto.EntityTypePermId(objectTypeId)
     let fo = new dto.SampleTypeFetchOptions()
-    fo.withPropertyAssignments().withPropertyType().withMaterialType()
-    fo.withPropertyAssignments().withPropertyType().withVocabulary()
-    fo.withPropertyAssignments().sortBy().ordinal()
+    fo.withPropertyAssignments()
+      .withPropertyType()
+      .withMaterialType()
+    fo.withPropertyAssignments()
+      .withPropertyType()
+      .withVocabulary()
+    fo.withPropertyAssignments()
+      .sortBy()
+      .ordinal()
 
     return facade.getSampleTypes([id], fo).then(map => {
       let objectType = map[objectTypeId]
-      if(objectType){
+      if (objectType) {
         return {
           code: objectType.code,
-          properties: objectType.propertyAssignments.map((assignment, index) => ({
-            id: index + 1,
-            ordinal: index + 1,
-            propertyType: assignment.propertyType,
-            mandatory: assignment.mandatory,
-            original: {
-              ...assignment,
-              ordinal: index + 1
-            },
-            selected: false,
-            errors: {}
-          }))
+          properties: objectType.propertyAssignments.map(
+            (assignment, index) => ({
+              id: index + 1,
+              ordinal: index + 1,
+              propertyType: assignment.propertyType,
+              mandatory: assignment.mandatory,
+              original: {
+                ...assignment,
+                ordinal: index + 1
+              },
+              selected: false,
+              errors: {}
+            })
+          )
         }
-      }else{
+      } else {
         return null
       }
     })
   }
 
-  loadPropertyTypes(){
+  loadPropertyTypes() {
     let criteria = new dto.PropertyTypeSearchCriteria()
     let fo = new dto.PropertyTypeFetchOptions()
     fo.withVocabulary().withTerms()
@@ -132,40 +157,46 @@ class ObjectType extends React.Component {
     })
   }
 
-  handleChange(id, key, value){
-    this.setState((prevState) => {
-      let newProperties = prevState.objectType.properties.map((property) => {
-        if(property.id === id){
-          return {
-            ...property,
-            [key]: value
+  handleChange(id, key, value) {
+    this.setState(
+      prevState => {
+        let newProperties = prevState.objectType.properties.map(property => {
+          if (property.id === id) {
+            return {
+              ...property,
+              [key]: value
+            }
+          } else {
+            return property
+          }
+        })
+        return {
+          ...prevState,
+          objectType: {
+            ...prevState.objectType,
+            properties: newProperties
           }
-        }else{
-          return property
         }
-      })
-      return {
-        ...prevState,
-        objectType: {
-          ...prevState.objectType,
-          properties: newProperties
+      },
+      () => {
+        if (this.isObjectTypeValidated()) {
+          this.validate()
         }
       }
-    }, () => {
-      if(this.isObjectTypeValidated()){
-        this.validate()
-      }
-    })
+    )
   }
 
-  handleRemove(){
-    this.setState((prevState) => {
-      let newProperties = prevState.objectType.properties.reduce((array, property) => {
-        if(!property.selected){
-          array.push(property)
-        }
-        return array
-      }, [])
+  handleRemove() {
+    this.setState(prevState => {
+      let newProperties = prevState.objectType.properties.reduce(
+        (array, property) => {
+          if (!property.selected) {
+            array.push(property)
+          }
+          return array
+        },
+        []
+      )
 
       return {
         ...prevState,
@@ -177,8 +208,8 @@ class ObjectType extends React.Component {
     })
   }
 
-  handleAdd(){
-    this.setState((prevState) => {
+  handleAdd() {
+    this.setState(prevState => {
       let newProperties = prevState.objectType.properties.map(property => {
         return {
           ...property,
@@ -204,8 +235,8 @@ class ObjectType extends React.Component {
     })
   }
 
-  handleSelect(id){
-    this.setState((prevState) => ({
+  handleSelect(id) {
+    this.setState(prevState => ({
       ...prevState,
       objectType: {
         ...prevState.objectType,
@@ -219,11 +250,11 @@ class ObjectType extends React.Component {
     }))
   }
 
-  handleReorder(oldPropertyIndex, newPropertyIndex){
+  handleReorder(oldPropertyIndex, newPropertyIndex) {
     let oldProperties = this.state.objectType.properties
-    let newProperties = [ ...oldProperties ]
+    let newProperties = [...oldProperties]
 
-    let [ property ] = newProperties.splice(oldPropertyIndex, 1)
+    let [property] = newProperties.splice(oldPropertyIndex, 1)
     newProperties.splice(newPropertyIndex, 0, property)
     newProperties = newProperties.map((property, index) => {
       return {
@@ -233,7 +264,7 @@ class ObjectType extends React.Component {
       }
     })
 
-    this.setState((prevState) => ({
+    this.setState(prevState => ({
       ...prevState,
       objectType: {
         ...prevState.objectType,
@@ -242,17 +273,17 @@ class ObjectType extends React.Component {
     }))
   }
 
-  validate(){
+  validate() {
     let valid = true
 
     let newProperties = this.state.objectType.properties.map(property => {
       let errors = {}
 
-      if(!property.propertyType){
+      if (!property.propertyType) {
         errors['propertyType'] = 'Cannot be empty'
       }
 
-      if(_.size(errors) > 0){
+      if (_.size(errors) > 0) {
         valid = false
       }
 
@@ -262,7 +293,7 @@ class ObjectType extends React.Component {
       }
     })
 
-    this.setState((prevState) => ({
+    this.setState(prevState => ({
       ...prevState,
       validated: true,
       objectType: {
@@ -274,8 +305,8 @@ class ObjectType extends React.Component {
     return valid
   }
 
-  handleSave(){
-    if(this.validate()){
+  handleSave() {
+    if (this.validate()) {
       let update = new dto.SampleTypeUpdate()
       update.setTypeId(new dto.EntityTypePermId(this.props.objectId))
 
@@ -284,18 +315,29 @@ class ObjectType extends React.Component {
       this.state.objectType.properties.forEach(property => {
         let newProperty = new dto.PropertyAssignmentCreation()
 
-        if(property.original && property.propertyType.code === property.original.propertyType.code){
+        if (
+          property.original &&
+          property.propertyType.code === property.original.propertyType.code
+        ) {
           newProperty.ordinal = property.ordinal
-          newProperty.propertyTypeId = new dto.PropertyTypePermId(property.original.propertyType.code)
+          newProperty.propertyTypeId = new dto.PropertyTypePermId(
+            property.original.propertyType.code
+          )
           newProperty.section = property.original.section
-          newProperty.pluginId = property.original.plugin ? new dto.PluginPermId(property.original.plugin.name) : null
-          newProperty.initialValueForExistingEntities = property.original.initialValueForExistingEntities
+          newProperty.pluginId = property.original.plugin
+            ? new dto.PluginPermId(property.original.plugin.name)
+            : null
+          newProperty.initialValueForExistingEntities =
+            property.original.initialValueForExistingEntities
           newProperty.showInEditView = property.original.showInEditView
-          newProperty.showRawValueInForms = property.original.showRawValueInForms
+          newProperty.showRawValueInForms =
+            property.original.showRawValueInForms
           newProperty.mandatory = property.mandatory
-        }else{
+        } else {
           newProperty.ordinal = property.ordinal
-          newProperty.propertyTypeId = new dto.PropertyTypePermId(property.propertyType.code)
+          newProperty.propertyTypeId = new dto.PropertyTypePermId(
+            property.propertyType.code
+          )
           newProperty.mandatory = property.mandatory
         }
 
@@ -304,18 +346,21 @@ class ObjectType extends React.Component {
 
       update.getPropertyAssignments().set(newProperties)
 
-      facade.updateSampleTypes([update]).then(()=>{
-        this.load()
-      }, (error) => {
-        this.props.error(error)
-      })
+      facade.updateSampleTypes([update]).then(
+        () => {
+          this.load()
+        },
+        error => {
+          this.props.error(error)
+        }
+      )
     }
   }
 
   render() {
     logger.log(logger.DEBUG, 'ObjectType.render')
 
-    if(!this.state.loaded){
+    if (!this.state.loaded) {
       return <div></div>
     }
 
@@ -345,23 +390,25 @@ class ObjectType extends React.Component {
     )
   }
 
-  isPropertySelected(){
-    return this.state.objectType.properties.some((property) => {
+  isPropertySelected() {
+    return this.state.objectType.properties.some(property => {
       return property.selected
     })
   }
 
-  isObjectTypeValidated(){
+  isObjectTypeValidated() {
     return this.state.validated
   }
 
-  isObjectTypeChanged(){
+  isObjectTypeChanged() {
     return this.state.changed
   }
-
 }
 
 export default _.flow(
-  connect(null, mapDispatchToProps),
+  connect(
+    null,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(ObjectType)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypeFooter.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypeFooter.jsx
index 04282adde58f352648b291141913cb26ae8b08de..3978a73d8ecb4f48d3da83463905efe2bd8558a9 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypeFooter.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypeFooter.jsx
@@ -1,16 +1,15 @@
 import React from 'react'
 import Button from '@material-ui/core/Button'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   button: {
     marginRight: theme.spacing(2)
   }
 })
 
 class ObjectTypeFooter extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeFooter.render')
 
@@ -47,7 +46,6 @@ class ObjectTypeFooter extends React.Component {
       </div>
     )
   }
-
 }
 
 export default withStyles(styles)(ObjectTypeFooter)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypeForm.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypeForm.jsx
index b30ccca12bab1996f7323399816d2ff950803345..449c8ea9feaaddcce39bb1e998662df3a91ca16e 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypeForm.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypeForm.jsx
@@ -2,14 +2,12 @@ import _ from 'lodash'
 import React from 'react'
 import ObjectTypeTitle from './ObjectTypeTitle.jsx'
 import ObjectTypeTable from './ObjectTypeTable.jsx'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
-const styles = () => ({
-})
+const styles = () => ({})
 
 class ObjectTypeForm extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeForm.render')
 
@@ -18,11 +16,9 @@ class ObjectTypeForm extends React.Component {
 
     return (
       <div>
-        <ObjectTypeTitle
-          objectType={objectType}
-        />
+        <ObjectTypeTitle objectType={objectType} />
         <form>
-          {properties && properties.length > 0 &&
+          {properties && properties.length > 0 && (
             <ObjectTypeTable
               objectType={objectType}
               propertyTypes={propertyTypes}
@@ -30,14 +26,11 @@ class ObjectTypeForm extends React.Component {
               onReorder={this.props.onReorder}
               onChange={this.props.onChange}
             />
-          }
+          )}
         </form>
       </div>
     )
   }
-
 }
 
-export default _.flow(
-  withStyles(styles),
-)(ObjectTypeForm)
+export default _.flow(withStyles(styles))(ObjectTypeForm)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyCell.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyCell.jsx
index efc7b4b50987cc95fa6cfdb736509667374dd950..d80689480c5b4a814156403408503cd2467d0a38 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyCell.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyCell.jsx
@@ -1,7 +1,7 @@
 import _ from 'lodash'
 import React from 'react'
 import TableCell from '@material-ui/core/TableCell'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
@@ -11,7 +11,6 @@ const styles = () => ({
 })
 
 class ObjectTypePropertyCell extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'ObjectTypePropertyCell.render')
 
@@ -23,9 +22,6 @@ class ObjectTypePropertyCell extends React.Component {
       </TableCell>
     )
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(ObjectTypePropertyCell)
+export default _.flow(withStyles(styles))(ObjectTypePropertyCell)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyMandatory.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyMandatory.jsx
index 6efb8ab477e59207f17d5b841359013591beee7c..01a61706bdba0d23bf3a5ea623b22e2d14631fd3 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyMandatory.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyMandatory.jsx
@@ -2,38 +2,41 @@ import _ from 'lodash'
 import React from 'react'
 import Checkbox from '@material-ui/core/Checkbox'
 import EditableField from '../../common/form/EditableField.jsx'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
   checkbox: {
     padding: '0px'
-  },
+  }
 })
 
 class ObjectTypePropertyMandatory extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.renderField = this.renderField.bind(this)
     this.handleChange = this.handleChange.bind(this)
   }
 
-  handleChange(event){
+  handleChange(event) {
     event.stopPropagation()
-    this.props.onChange(this.props.property.id, 'mandatory', event.target.checked)
+    this.props.onChange(
+      this.props.property.id,
+      'mandatory',
+      event.target.checked
+    )
   }
 
-  render(){
+  render() {
     logger.log(logger.DEBUG, 'ObjectTypePropertyMandatory.render')
 
     return <EditableField renderField={this.renderField} />
   }
 
-  renderField({ref, edited, handleBlur}){
-    const {classes, property} = this.props
+  renderField({ ref, edited, handleBlur }) {
+    const { classes, property } = this.props
 
-    if(edited){
+    if (edited) {
       return (
         <Checkbox
           checked={property.mandatory}
@@ -44,15 +47,10 @@ class ObjectTypePropertyMandatory extends React.Component {
           classes={{ root: classes.checkbox }}
         />
       )
-    }else{
-      return (
-        <span>{property.mandatory ? 'true' : 'false'}</span>
-      )
+    } else {
+      return <span>{property.mandatory ? 'true' : 'false'}</span>
     }
   }
-
 }
 
-export default _.flow(
-  withStyles(styles),
-)(ObjectTypePropertyMandatory)
+export default _.flow(withStyles(styles))(ObjectTypePropertyMandatory)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyPreview.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyPreview.jsx
index bf7cc5d1ad825b08c664f8f3d2779550bdae0c97..c28d9cfa36827cae729fa7683383b02bf5bc3519 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyPreview.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyPreview.jsx
@@ -5,8 +5,8 @@ import TextField from '@material-ui/core/TextField'
 import Checkbox from '@material-ui/core/Checkbox'
 import InfoIcon from '@material-ui/icons/InfoOutlined'
 import Tooltip from '@material-ui/core/Tooltip'
-import {withStyles} from '@material-ui/core/styles'
-import {facade, dto} from '../../../services/openbis.js'
+import { withStyles } from '@material-ui/core/styles'
+import { facade, dto } from '../../../services/openbis.js'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
@@ -21,48 +21,49 @@ const styles = () => ({
 })
 
 class ObjectTypePropertyPreview extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.state = {
       vocabularyValue: '',
-      materialValue: '',
+      materialValue: ''
     }
     this.setMaterial = this.setMaterial.bind(this)
     this.setVocabulary = this.setVocabulary.bind(this)
   }
 
   static getDerivedStateFromProps(props, state) {
-    if(!state.property
-      || state.property.propertyType !== props.property.propertyType
-      || state.property.mandatory !== props.property.mandatory){
+    if (
+      !state.property ||
+      state.property.propertyType !== props.property.propertyType ||
+      state.property.mandatory !== props.property.mandatory
+    ) {
       return {
         loaded: false,
         property: props.property,
         vocabularyTerms: [],
         materials: []
       }
-    }else{
+    } else {
       return null
     }
   }
 
-  componentDidMount(){
+  componentDidMount() {
     this.load()
   }
 
-  componentDidUpdate(){
+  componentDidUpdate() {
     this.load()
   }
 
-  load(){
-    if(this.state.loaded){
+  load() {
+    if (this.state.loaded) {
       return
     }
 
-    const {propertyType} = this.state.property
+    const { propertyType } = this.state.property
 
-    switch(propertyType.dataType){
+    switch (propertyType.dataType) {
       case 'CONTROLLEDVOCABULARY':
         this.loadVocabulary()
         return
@@ -77,13 +78,18 @@ class ObjectTypePropertyPreview extends React.Component {
     }
   }
 
-  render(){
+  render() {
     logger.log(logger.DEBUG, 'ObjectTypePropertyPreview.render')
 
-    const {classes} = this.props
+    const { classes } = this.props
 
     return (
-      <div className={classes.container} onClick={(event) => {event.stopPropagation()}}>
+      <div
+        className={classes.container}
+        onClick={event => {
+          event.stopPropagation()
+        }}
+      >
         {this.renderField()}
         <Tooltip title={this.getDescription()}>
           <InfoIcon />
@@ -92,10 +98,10 @@ class ObjectTypePropertyPreview extends React.Component {
     )
   }
 
-  renderField(){
-    const {propertyType} = this.state.property
+  renderField() {
+    const { propertyType } = this.state.property
 
-    switch(propertyType.dataType){
+    switch (propertyType.dataType) {
       case 'BOOLEAN':
         return this.renderBoolean()
       case 'VARCHAR':
@@ -114,53 +120,53 @@ class ObjectTypePropertyPreview extends React.Component {
     }
   }
 
-  renderBoolean(){
-    const {classes} = this.props
+  renderBoolean() {
+    const { classes } = this.props
     return (
-      <FormControlLabel classes={{ root: classes.boolean }}
+      <FormControlLabel
+        classes={{ root: classes.boolean }}
         control={<Checkbox />}
         label={this.getLabel()}
       />
     )
   }
 
-  renderVarchar(){
+  renderVarchar() {
     return (
-      <TextField
-        label={this.getLabel()}
-        fullWidth={true}
-        variant="filled"
-      />
+      <TextField label={this.getLabel()} fullWidth={true} variant='filled' />
     )
   }
 
-  renderMultilineVarchar(){
+  renderMultilineVarchar() {
     return (
       <TextField
         label={this.getLabel()}
         multiline={true}
         fullWidth={true}
-        variant="filled"
+        variant='filled'
       />
     )
   }
 
-  renderNumber(){
+  renderNumber() {
     return (
       <TextField
         label={this.getLabel()}
-        type="number"
+        type='number'
         fullWidth={true}
-        variant="filled"
+        variant='filled'
       />
     )
   }
 
-  loadVocabulary(){
+  loadVocabulary() {
     let criteria = new dto.VocabularyTermSearchCriteria()
     let fo = new dto.VocabularyTermFetchOptions()
 
-    criteria.withVocabulary().withCode().thatEquals(this.state.property.propertyType.vocabulary.code)
+    criteria
+      .withVocabulary()
+      .withCode()
+      .thatEquals(this.state.property.propertyType.vocabulary.code)
 
     return facade.searchVocabularyTerms(criteria, fo).then(result => {
       this.setState(() => ({
@@ -170,45 +176,50 @@ class ObjectTypePropertyPreview extends React.Component {
     })
   }
 
-  getVocabulary(){
+  getVocabulary() {
     return this.state.vocabularyValue
   }
 
-  setVocabulary(event){
+  setVocabulary(event) {
     const value = event.target.value
     this.setState(() => ({
       vocabularyValue: value
     }))
   }
 
-  renderVocabulary(){
+  renderVocabulary() {
     return (
       <TextField
         select
         SelectProps={{
-          native: true,
+          native: true
         }}
         label={this.getLabel()}
         value={this.getVocabulary()}
         onChange={this.setVocabulary}
         fullWidth={true}
-        variant="filled"
+        variant='filled'
       >
-        <option value=""></option>
+        <option value=''></option>
         {this.state.vocabularyTerms.map(term => (
-          <option key={term.code} value={term.code}>{term.label || term.code}</option>
+          <option key={term.code} value={term.code}>
+            {term.label || term.code}
+          </option>
         ))}
       </TextField>
     )
   }
 
-  loadMaterial(){
+  loadMaterial() {
     let criteria = new dto.MaterialSearchCriteria()
     let fo = new dto.MaterialFetchOptions()
 
     let materialType = this.state.property.propertyType.materialType
-    if(materialType){
-      criteria.withType().withId().thatEquals(materialType.permId)
+    if (materialType) {
+      criteria
+        .withType()
+        .withId()
+        .thatEquals(materialType.permId)
     }
 
     return facade.searchMaterials(criteria, fo).then(result => {
@@ -219,54 +230,53 @@ class ObjectTypePropertyPreview extends React.Component {
     })
   }
 
-  getMaterial(){
+  getMaterial() {
     return this.state.materialValue
   }
 
-  setMaterial(event){
+  setMaterial(event) {
     const value = event.target.value
     this.setState(() => ({
       materialValue: value
     }))
   }
 
-  renderMaterial(){
+  renderMaterial() {
     return (
       <TextField
         select
         SelectProps={{
-          native: true,
+          native: true
         }}
         label={this.getLabel()}
         value={this.getMaterial()}
         onChange={this.setMaterial}
         fullWidth={true}
-        variant="filled"
+        variant='filled'
       >
-        <option value=""></option>
+        <option value=''></option>
         {this.state.materials.map(material => (
-          <option key={material.code} value={material.code}>{material.code}</option>
+          <option key={material.code} value={material.code}>
+            {material.code}
+          </option>
         ))}
       </TextField>
     )
   }
 
-  renderUnsupported(){
-    return (<div>unsupported</div>)
+  renderUnsupported() {
+    return <div>unsupported</div>
   }
 
-  getLabel(){
+  getLabel() {
     let mandatory = this.state.property.mandatory
     let label = this.state.property.propertyType.label
     return mandatory ? label + '*' : label
   }
 
-  getDescription(){
+  getDescription() {
     return this.state.property.propertyType.description
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(ObjectTypePropertyPreview)
+export default _.flow(withStyles(styles))(ObjectTypePropertyPreview)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyRow.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyRow.jsx
index f626780e6c1a5901e916d9db469e1ee96c5e5699..ca7b85739096c268e50dd493460e1433e3951db8 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyRow.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyRow.jsx
@@ -9,10 +9,10 @@ import ObjectTypePropertyCell from './ObjectTypePropertyCell.jsx'
 import ObjectTypePropertyPreview from './ObjectTypePropertyPreview.jsx'
 import ObjectTypePropertyType from './ObjectTypePropertyType.jsx'
 import ObjectTypePropertyMandatory from './ObjectTypePropertyMandatory.jsx'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
-const styles = (theme) => ({
+const styles = theme => ({
   row: {
     backgroundColor: theme.palette.background.paper,
     '&:hover': {
@@ -34,7 +34,7 @@ const source = {
     return { sourceIndex: props.index }
   },
   endDrag(props, monitor) {
-    if(monitor.getItem() && monitor.getDropResult()){
+    if (monitor.getItem() && monitor.getDropResult()) {
       const { sourceIndex } = monitor.getItem()
       const { targetIndex } = monitor.getDropResult()
       props.onReorder(sourceIndex, targetIndex)
@@ -64,28 +64,27 @@ function targetCollect(connect, monitor) {
 }
 
 class ObjectTypePropertyRow extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.handleRef = React.createRef()
     this.rowRef = React.createRef()
     this.handleSelect = this.handleSelect.bind(this)
   }
 
-  componentDidMount(){
+  componentDidMount() {
     this.props.connectDragSource(this.handleRef.current)
     this.props.connectDragPreview(this.rowRef.current)
     this.props.connectDropTarget(this.rowRef.current)
   }
 
-  handleSelect(){
+  handleSelect() {
     this.props.onSelect(this.props.property.id)
   }
 
-  render(){
+  render() {
     logger.log(logger.DEBUG, 'ObjectTypePropertyRow.render')
 
-    const {classes, property} = this.props
+    const { classes, property } = this.props
 
     return (
       <RootRef rootRef={this.rowRef}>
@@ -115,22 +114,18 @@ class ObjectTypePropertyRow extends React.Component {
     )
   }
 
-  renderPreview(){
-    const {property} = this.props
-
-    if(property.propertyType){
-      return (
-        <ObjectTypePropertyPreview
-          property={property}
-        />
-      )
-    }else{
-      return (<div></div>)
+  renderPreview() {
+    const { property } = this.props
+
+    if (property.propertyType) {
+      return <ObjectTypePropertyPreview property={property} />
+    } else {
+      return <div></div>
     }
   }
 
-  renderPropertyType(){
-    const {property, propertyTypes, onChange} = this.props
+  renderPropertyType() {
+    const { property, propertyTypes, onChange } = this.props
 
     return (
       <ObjectTypePropertyType
@@ -141,16 +136,12 @@ class ObjectTypePropertyRow extends React.Component {
     )
   }
 
-  renderMandatory(){
-    const {property, onChange} = this.props
+  renderMandatory() {
+    const { property, onChange } = this.props
     return (
-      <ObjectTypePropertyMandatory
-        property={property}
-        onChange={onChange}
-      />
+      <ObjectTypePropertyMandatory property={property} onChange={onChange} />
     )
   }
-
 }
 
 export default _.flow(
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyType.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyType.jsx
index 44ab9aa7db79c38cf61a4ba031ce6d07cee662d2..7f7b121edd19660d91441671a7ea61083d1723c3 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyType.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypePropertyType.jsx
@@ -6,14 +6,13 @@ import EditableField from '../../common/form/EditableField.jsx'
 import logger from '../../../common/logger.js'
 
 class ObjectTypePropertyType extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.renderField = this.renderField.bind(this)
     this.handleChange = this.handleChange.bind(this)
   }
 
-  handleChange(event){
+  handleChange(event) {
     event.stopPropagation()
     let propertyType = _.find(this.props.propertyTypes, propertyType => {
       return propertyType.code === event.target.value
@@ -21,23 +20,23 @@ class ObjectTypePropertyType extends React.Component {
     this.props.onChange(this.props.property.id, 'propertyType', propertyType)
   }
 
-  render(){
+  render() {
     logger.log(logger.DEBUG, 'ObjectTypePropertyType.render')
 
     return <EditableField renderField={this.renderField} />
   }
 
-  renderField({ref, edited, handleBlur}){
-    const {property, propertyTypes} = this.props
-    const {propertyType} = property
+  renderField({ ref, edited, handleBlur }) {
+    const { property, propertyTypes } = this.props
+    const { propertyType } = property
 
-    if(edited){
+    if (edited) {
       return (
         <TextField
           inputRef={ref}
           select
           SelectProps={{
-            native: true,
+            native: true
           }}
           value={propertyType ? propertyType.code : ''}
           onChange={this.handleChange}
@@ -46,32 +45,34 @@ class ObjectTypePropertyType extends React.Component {
           error={this.hasError()}
           helperText={this.getError()}
         >
-          <option value=""></option>
-          {propertyTypes && propertyTypes.map(propertyType => (
-            <option key={propertyType.code} value={propertyType.code}>{propertyType.code}</option>
-          ))}
+          <option value=''></option>
+          {propertyTypes &&
+            propertyTypes.map(propertyType => (
+              <option key={propertyType.code} value={propertyType.code}>
+                {propertyType.code}
+              </option>
+            ))}
         </TextField>
       )
-    }else{
+    } else {
       return (
         <div>
           <div>{propertyType ? propertyType.code : ''}</div>
-          {this.hasError() &&
+          {this.hasError() && (
             <FormHelperText error={true}>{this.getError()}</FormHelperText>
-          }
+          )}
         </div>
       )
     }
   }
 
-  hasError(){
+  hasError() {
     return this.getError() ? true : false
   }
 
-  getError(){
+  getError() {
     return this.props.property.errors['propertyType']
   }
-
 }
 
 export default ObjectTypePropertyType
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypeTable.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypeTable.jsx
index 36ff07cc35cd338d5c311eaeb56add8396879d6d..350901330d665b32528507b0a725d34b7dbdc5e8 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypeTable.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypeTable.jsx
@@ -6,7 +6,7 @@ import TableHead from '@material-ui/core/TableHead'
 import TableRow from '@material-ui/core/TableRow'
 import ObjectTypePropertyCell from './ObjectTypePropertyCell.jsx'
 import ObjectTypePropertyRow from './ObjectTypePropertyRow.jsx'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
@@ -19,8 +19,7 @@ const styles = () => ({
 })
 
 class ObjectTypeTable extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
     this.state = {}
   }
@@ -57,11 +56,9 @@ class ObjectTypeTable extends React.Component {
             </React.Fragment>
           ))}
         </TableBody>
-      </Table>)
+      </Table>
+    )
   }
-
 }
 
-export default _.flow(
-  withStyles(styles)
-)(ObjectTypeTable)
+export default _.flow(withStyles(styles))(ObjectTypeTable)
diff --git a/openbis_ng_ui/src/components/types/objectType/ObjectTypeTitle.jsx b/openbis_ng_ui/src/components/types/objectType/ObjectTypeTitle.jsx
index 8b1f632ad9fef63a13cf103794d0fb0880295cb5..c57adcebe5c9923d9023f9d8e9d2194ba507cf12 100644
--- a/openbis_ng_ui/src/components/types/objectType/ObjectTypeTitle.jsx
+++ b/openbis_ng_ui/src/components/types/objectType/ObjectTypeTitle.jsx
@@ -3,19 +3,13 @@ import Typography from '@material-ui/core/Typography'
 import logger from '../../../common/logger.js'
 
 class ObjectTypeTitle extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'ObjectTypeHeader.render')
 
-    const {objectType} = this.props
+    const { objectType } = this.props
 
-    return (
-      <Typography variant="h6">
-        {objectType.code}
-      </Typography>
-    )
+    return <Typography variant='h6'>{objectType.code}</Typography>
   }
-
 }
 
 export default ObjectTypeTitle
diff --git a/openbis_ng_ui/src/components/types/search/Search.jsx b/openbis_ng_ui/src/components/types/search/Search.jsx
index 4c9591e0cf599a527275733b36c400333a916c33..c524ad3954f3ce7af35587d1a5e213491d390a2e 100644
--- a/openbis_ng_ui/src/components/types/search/Search.jsx
+++ b/openbis_ng_ui/src/components/types/search/Search.jsx
@@ -1,14 +1,14 @@
 import _ from 'lodash'
 import React from 'react'
-import {connect} from 'react-redux'
-import {withStyles} from '@material-ui/core/styles'
+import { connect } from 'react-redux'
+import { withStyles } from '@material-ui/core/styles'
 import Link from '@material-ui/core/Link'
 import Grid from '../../common/grid/Grid.jsx'
 import * as ids from '../../../common/consts/ids.js'
 import * as pages from '../../../common/consts/pages.js'
 import * as objectTypes from '../../../common/consts/objectType.js'
 import * as actions from '../../../store/actions/actions.js'
-import {facade, dto} from '../../../services/openbis.js'
+import { facade, dto } from '../../../services/openbis.js'
 import logger from '../../../common/logger.js'
 
 const styles = () => ({
@@ -17,15 +17,16 @@ const styles = () => ({
   }
 })
 
-function mapDispatchToProps(dispatch){
+function mapDispatchToProps(dispatch) {
   return {
-    objectOpen: (objectType, objectId) => { dispatch(actions.objectOpen(pages.TYPES, objectType, objectId)) }
+    objectOpen: (objectType, objectId) => {
+      dispatch(actions.objectOpen(pages.TYPES, objectType, objectId))
+    }
   }
 }
 
 class Search extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
 
     this.state = {
@@ -35,31 +36,45 @@ class Search extends React.Component {
     this.handleLinkClick = this.handleLinkClick.bind(this)
   }
 
-  componentDidMount(){
+  componentDidMount() {
     this.load().then(types => {
-      this.setState(()=>({
+      this.setState(() => ({
         types,
         loaded: true
       }))
     })
   }
 
-  load(){
+  load() {
     return Promise.all([
       this.searchObjectTypes(),
       this.searchCollectionTypes(),
       this.searchDataSetTypes(),
       this.searchMaterialTypes()
     ]).then(([objectTypes, collectionTypes, dataSetTypes, materialTypes]) => {
-      let allTypes = [].concat(objectTypes, collectionTypes, dataSetTypes, materialTypes)
-      return allTypes.map(type => ({
-        ...type,
-        id: type.permId.entityKind + '-' + type.permId.permId
-      }))
+      let allTypes = [].concat(
+        objectTypes,
+        collectionTypes,
+        dataSetTypes,
+        materialTypes
+      )
+
+      let query = this.props.objectId.toUpperCase()
+
+      return allTypes
+        .filter(
+          type =>
+            (type.code && type.code.toUpperCase().includes(query)) ||
+            (type.description && type.description.toUpperCase().includes(query))
+        )
+        .map(type => ({
+          ...type,
+          id: type.permId.entityKind + '-' + type.permId.permId
+        }))
     })
   }
 
-  searchObjectTypes(){
+  searchObjectTypes() {
     let criteria = new dto.SampleTypeSearchCriteria()
     let fo = new dto.SampleTypeFetchOptions()
     return facade.searchSampleTypes(criteria, fo).then(result => {
@@ -67,7 +82,7 @@ class Search extends React.Component {
     })
   }
 
-  searchCollectionTypes(){
+  searchCollectionTypes() {
     let criteria = new dto.ExperimentTypeSearchCriteria()
     let fo = new dto.ExperimentTypeFetchOptions()
     return facade.searchExperimentTypes(criteria, fo).then(result => {
@@ -75,7 +90,7 @@ class Search extends React.Component {
     })
   }
 
-  searchDataSetTypes(){
+  searchDataSetTypes() {
     let criteria = new dto.DataSetTypeSearchCriteria()
     let fo = new dto.DataSetTypeFetchOptions()
     return facade.searchDataSetTypes(criteria, fo).then(result => {
@@ -83,7 +98,7 @@ class Search extends React.Component {
     })
   }
 
-  searchMaterialTypes(){
+  searchMaterialTypes() {
     let criteria = new dto.MaterialTypeSearchCriteria()
     let fo = new dto.MaterialTypeFetchOptions()
     return facade.searchMaterialTypes(criteria, fo).then(result => {
@@ -91,22 +106,25 @@ class Search extends React.Component {
     })
   }
 
-  handleLinkClick(permId){
+  handleLinkClick(permId) {
     const entityKindToObjecType = {
-      'SAMPLE': objectTypes.OBJECT_TYPE,
-      'EXPERIMENT': objectTypes.COLLECTION_TYPE,
-      'DATA_SET': objectTypes.DATA_SET_TYPE,
-      'MATERIAL': objectTypes.MATERIAL_TYPE
+      SAMPLE: objectTypes.OBJECT_TYPE,
+      EXPERIMENT: objectTypes.COLLECTION_TYPE,
+      DATA_SET: objectTypes.DATA_SET_TYPE,
+      MATERIAL: objectTypes.MATERIAL_TYPE
     }
     return () => {
-      this.props.objectOpen(entityKindToObjecType[permId.entityKind], permId.permId)
+      this.props.objectOpen(
+        entityKindToObjecType[permId.entityKind],
+        permId.permId
+      )
     }
   }
 
   render() {
     logger.log(logger.DEBUG, 'Search.render')
 
-    if(!this.state.loaded){
+    if (!this.state.loaded) {
       return null
     }
 
@@ -116,34 +134,37 @@ class Search extends React.Component {
     return (
       <Grid
         id={ids.TYPES_GRID_ID}
-        filter={this.props.objectId}
         columns={[
           {
             field: 'permId.entityKind',
-            label: 'Kind',
+            label: 'Kind'
           },
           {
             field: 'code',
             render: row => (
               <Link
-                component="button"
+                component='button'
                 classes={{ root: classes.tableLink }}
-                onClick={this.handleLinkClick(row.permId)}>{row.code}
+                onClick={this.handleLinkClick(row.permId)}
+              >
+                {row.code}
               </Link>
             )
           },
           {
-            field: 'description',
+            field: 'description'
           }
         ]}
         data={types}
       />
     )
   }
-
 }
 
 export default _.flow(
-  connect(null, mapDispatchToProps),
+  connect(
+    null,
+    mapDispatchToProps
+  ),
   withStyles(styles)
 )(Search)
diff --git a/openbis_ng_ui/src/components/users/Users.jsx b/openbis_ng_ui/src/components/users/Users.jsx
index 816abd7bfa20f6a3c9f0aaea6d3b62b2afad8158..12a5cf3698bca562785a74fc6a8d7f958cc2e74c 100644
--- a/openbis_ng_ui/src/components/users/Users.jsx
+++ b/openbis_ng_ui/src/components/users/Users.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {withStyles} from '@material-ui/core/styles'
+import { withStyles } from '@material-ui/core/styles'
 import logger from '../../common/logger.js'
 import * as pages from '../../common/consts/pages.js'
 import * as objectType from '../../common/consts/objectType.js'
@@ -15,7 +15,7 @@ const styles = () => ({
   container: {
     display: 'flex',
     width: '100%'
-  },
+  }
 })
 
 const objectTypeToComponent = {
@@ -25,7 +25,6 @@ const objectTypeToComponent = {
 }
 
 class Users extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Users.render')
 
@@ -33,12 +32,14 @@ class Users extends React.Component {
 
     return (
       <div className={classes.container}>
-        <Browser page={pages.USERS}/>
-        <Content page={pages.USERS} objectTypeToComponent={objectTypeToComponent}/>
+        <Browser page={pages.USERS} />
+        <Content
+          page={pages.USERS}
+          objectTypeToComponent={objectTypeToComponent}
+        />
       </div>
     )
   }
-
 }
 
 export default withStyles(styles)(Users)
diff --git a/openbis_ng_ui/src/components/users/group/Group.jsx b/openbis_ng_ui/src/components/users/group/Group.jsx
index a1fa93d6cd098f824148d50ccd5238bb53fe777b..ba07050d541d7253df13582a88e694ffc1a2bbed 100644
--- a/openbis_ng_ui/src/components/users/group/Group.jsx
+++ b/openbis_ng_ui/src/components/users/group/Group.jsx
@@ -2,12 +2,10 @@ import React from 'react'
 import logger from '../../../common/logger.js'
 
 class Group extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'Group.render')
     return <div>Group</div>
   }
-
 }
 
 export default Group
diff --git a/openbis_ng_ui/src/components/users/search/Search.jsx b/openbis_ng_ui/src/components/users/search/Search.jsx
index 03a26ce48bbd1f52b94eae2830c7dc138cb1cb90..337e8bbd177033426570cfa08e5cbd73d50fb8f2 100644
--- a/openbis_ng_ui/src/components/users/search/Search.jsx
+++ b/openbis_ng_ui/src/components/users/search/Search.jsx
@@ -1,48 +1,58 @@
 import React from 'react'
 import Grid from '../../common/grid/Grid.jsx'
 import * as ids from '../../../common/consts/ids.js'
-import {facade, dto} from '../../../services/openbis.js'
+import { facade, dto } from '../../../services/openbis.js'
 import logger from '../../../common/logger.js'
 
 class Search extends React.Component {
-
-  constructor(props){
+  constructor(props) {
     super(props)
 
+    this.state = {
+      loaded: false
+    }
+
     this.load = this.load.bind(this)
   }
 
-  load({ filter, page, pageSize }){
+  componentDidMount() {
+    this.load().then(users => {
+      this.setState(() => ({
+        users,
+        loaded: true
+      }))
+    })
+  }
+
+  load() {
+    let query = this.props.objectId
+
     let criteria = new dto.PersonSearchCriteria()
     let fo = new dto.PersonFetchOptions()
 
     criteria.withOrOperator()
-    criteria.withUserId().thatContains(filter)
-    criteria.withFirstName().thatContains(filter)
-    criteria.withLastName().thatContains(filter)
-
-    fo.count(pageSize)
-    fo.from(page*pageSize)
+    criteria.withUserId().thatContains(query)
+    criteria.withFirstName().thatContains(query)
+    criteria.withLastName().thatContains(query)
 
     return facade.searchPersons(criteria, fo).then(result => {
-      let objects = result.objects.map(user => ({
+      return result.objects.map(user => ({
         ...user,
         id: user.userId
       }))
-      return {
-        objects,
-        totalCount: result.totalCount
-      }
     })
   }
 
   render() {
     logger.log(logger.DEBUG, 'Search.render')
 
+    if (!this.state.loaded) {
+      return null
+    }
+
     return (
       <Grid
         id={ids.USERS_GRID_ID}
-        filter={this.props.objectId}
         columns={[
           {
             field: 'userId'
@@ -54,11 +64,10 @@ class Search extends React.Component {
             field: 'lastName'
           }
         ]}
-        data={this.load}
+        data={this.state.users}
       />
     )
   }
-
 }
 
 export default Search
diff --git a/openbis_ng_ui/src/components/users/user/User.jsx b/openbis_ng_ui/src/components/users/user/User.jsx
index 8642343df0b760bf07e0a7b83ca21466f1d4fe2a..c81f4aafa3be357ad2730f24a440bee8f33c85c4 100644
--- a/openbis_ng_ui/src/components/users/user/User.jsx
+++ b/openbis_ng_ui/src/components/users/user/User.jsx
@@ -2,12 +2,10 @@ import React from 'react'
 import logger from '../../../common/logger.js'
 
 class User extends React.Component {
-
   render() {
     logger.log(logger.DEBUG, 'User.render')
     return <div>User</div>
   }
-
 }
 
 export default User
diff --git a/openbis_ng_ui/src/index.js b/openbis_ng_ui/src/index.js
index 5cb15aae416b8b02f1c24c0339201364306dab2d..a8a9cf0cefd21b48efcf057edff4c3c2f352c5b1 100644
--- a/openbis_ng_ui/src/index.js
+++ b/openbis_ng_ui/src/index.js
@@ -26,10 +26,10 @@ const render = () => {
   const store = require('./store/store.js').default
 
   ReactDOM.render(
-    <Provider store = { store }>
-      <MuiThemeProvider theme={ theme }>
+    <Provider store={store}>
+      <MuiThemeProvider theme={theme}>
         <DragAndDropProvider>
-            <App />
+          <App />
         </DragAndDropProvider>
       </MuiThemeProvider>
     </Provider>,
diff --git a/openbis_ng_ui/src/profile.js b/openbis_ng_ui/src/profile.js
index cc4eef0850e2434e60ae6b0192f7e2bae6842831..115711c3990626d01d0f3212c59fcc8ce069390d 100644
--- a/openbis_ng_ui/src/profile.js
+++ b/openbis_ng_ui/src/profile.js
@@ -2,4 +2,4 @@ let profile = {
   devEmail: 'dummy.address@test.ch'
 }
 
-export default profile
\ No newline at end of file
+export default profile
diff --git a/openbis_ng_ui/src/services/openbis/dto.js b/openbis_ng_ui/src/services/openbis/dto.js
index 84c4f0f8e03dae0a1438a8340f6ffbf2feded06d..64881181464bd80e4a39570fc90d0f0825641938 100644
--- a/openbis_ng_ui/src/services/openbis/dto.js
+++ b/openbis_ng_ui/src/services/openbis/dto.js
@@ -26,27 +26,32 @@ const CLASS_FULL_NAMES = [
   'as/dto/vocabulary/search/VocabularyTermSearchCriteria',
   'as/dto/vocabulary/fetchoptions/VocabularyTermFetchOptions',
   'as/dto/plugin/id/PluginPermId',
-  'as/dto/webapp/create/WebAppSettingCreation',
+  'as/dto/webapp/create/WebAppSettingCreation'
 ]
 
 class Dto {
-
-  init(){
+  init() {
     let _this = this
 
-    let load = function(index){
+    let load = function(index) {
       return new Promise((resolve, reject) => {
-        if(index < CLASS_FULL_NAMES.length){
+        if (index < CLASS_FULL_NAMES.length) {
           let classFullName = CLASS_FULL_NAMES[index]
-          let className = classFullName.substring(classFullName.lastIndexOf('/') + 1)
+          let className = classFullName.substring(
+            classFullName.lastIndexOf('/') + 1
+          )
           /* eslint-disable-next-line no-undef */
-          requirejs([classFullName], clazz => {
-            _this[className] = clazz
-            return load(index + 1).then(resolve, reject)
-          }, error => {
-            reject(error)
-          })
-        }else{
+          requirejs(
+            [classFullName],
+            clazz => {
+              _this[className] = clazz
+              return load(index + 1).then(resolve, reject)
+            },
+            error => {
+              reject(error)
+            }
+          )
+        } else {
           resolve()
         }
       })
@@ -54,14 +59,13 @@ class Dto {
 
     return load(0)
   }
-
 }
 
 const dto = new Dto()
 
 CLASS_FULL_NAMES.forEach(classFullName => {
   let className = classFullName.substring(classFullName.lastIndexOf('/') + 1)
-  dto[className] = function(){}
+  dto[className] = function() {}
 })
 
 export { dto }
diff --git a/openbis_ng_ui/src/services/openbis/facade.js b/openbis_ng_ui/src/services/openbis/facade.js
index 4fa673a17f3eeefcbfbad7e7283c0097cbadc6dd..a1626904dddc93a750642e6ad4467561669b9163 100644
--- a/openbis_ng_ui/src/services/openbis/facade.js
+++ b/openbis_ng_ui/src/services/openbis/facade.js
@@ -1,30 +1,35 @@
 const autoBind = require('auto-bind')
 
 export class Facade {
-
   constructor() {
     autoBind(this)
   }
 
-  init(){
+  init() {
     let _this = this
     return new Promise((resolve, reject) => {
       /* eslint-disable-next-line no-undef */
-      requirejs(['openbis'], openbis => {
-        _this.v3 = new openbis()
-        resolve()
-      }, error => {
-        reject(error)
-      })
+      requirejs(
+        ['openbis'],
+        openbis => {
+          _this.v3 = new openbis()
+          resolve()
+        },
+        error => {
+          reject(error)
+        }
+      )
     })
   }
 
   login(user, password) {
     let v3 = this.v3
     return new Promise((resolve, reject) => {
-      v3.login(user, password).done(resolve).fail(() => {
-        reject({message: 'Login failed'})
-      })
+      v3.login(user, password)
+        .done(resolve)
+        .fail(() => {
+          reject({ message: 'Login failed' })
+        })
     })
   }
 
@@ -32,11 +37,11 @@ export class Facade {
     return this.v3.logout()
   }
 
-  getPersons(ids, fo){
+  getPersons(ids, fo) {
     return this.v3.getPersons(ids, fo)
   }
 
-  updatePersons(updates){
+  updatePersons(updates) {
     return this.v3.updatePersons(updates)
   }
 
@@ -60,7 +65,7 @@ export class Facade {
     return this.v3.searchAuthorizationGroups(criteria, fo)
   }
 
-  getSampleTypes(ids, fo){
+  getSampleTypes(ids, fo) {
     return this.v3.getSampleTypes(ids, fo)
   }
 
@@ -68,7 +73,7 @@ export class Facade {
     return this.v3.searchSampleTypes(criteria, fo)
   }
 
-  updateSampleTypes(updates){
+  updateSampleTypes(updates) {
     return this.v3.updateSampleTypes(updates)
   }
 
@@ -83,7 +88,6 @@ export class Facade {
   searchMaterialTypes(criteria, fo) {
     return this.v3.searchMaterialTypes(criteria, fo)
   }
-
 }
 
 const facade = new Facade()
diff --git a/openbis_ng_ui/src/store/actions/api.js b/openbis_ng_ui/src/store/actions/api.js
index 2a785b43c2f951f91d3c3ece5f42042ffbe59867..e5a3338db543b25df8727a0182e57088c6f4baab 100644
--- a/openbis_ng_ui/src/store/actions/api.js
+++ b/openbis_ng_ui/src/store/actions/api.js
@@ -2,7 +2,7 @@ export const API_REQUEST = 'API_REQUEST'
 export const API_SUCCESS = 'API_SUCCESS'
 export const API_ERROR = 'API_ERROR'
 
-export const apiRequest = ({method, params, meta}) => ({
+export const apiRequest = ({ method, params, meta }) => ({
   type: API_REQUEST,
   payload: {
     method,
@@ -11,7 +11,7 @@ export const apiRequest = ({method, params, meta}) => ({
   meta
 })
 
-export const apiSuccess = ({result, meta}) => ({
+export const apiSuccess = ({ result, meta }) => ({
   type: API_SUCCESS,
   payload: {
     result
@@ -19,7 +19,7 @@ export const apiSuccess = ({result, meta}) => ({
   meta
 })
 
-export const apiError = ({error, meta}) => ({
+export const apiError = ({ error, meta }) => ({
   type: API_ERROR,
   payload: {
     error
diff --git a/openbis_ng_ui/src/store/actions/app.js b/openbis_ng_ui/src/store/actions/app.js
index e2bccce38ed366468bafe6003d2daf409fe489ba..997e5d1fc003d8281c1cf7cfce9e844d2dcac535 100644
--- a/openbis_ng_ui/src/store/actions/app.js
+++ b/openbis_ng_ui/src/store/actions/app.js
@@ -37,63 +37,63 @@ export const search = (page, text) => ({
   }
 })
 
-export const currentPageChange = (currentPage) => ({
+export const currentPageChange = currentPage => ({
   type: CURRENT_PAGE_CHANGE,
   payload: {
     currentPage
   }
 })
 
-export const searchChange = (search) => ({
+export const searchChange = search => ({
   type: SEARCH_CHANGE,
   payload: {
     search
   }
 })
 
-export const errorChange = (error) => ({
+export const errorChange = error => ({
   type: ERROR_CHANGE,
   payload: {
     error
   }
 })
 
-export const routeChange = (route) => ({
+export const routeChange = route => ({
   type: ROUTE_CHANGE,
   payload: {
     route
   }
 })
 
-export const setLoading = (loading) => ({
+export const setLoading = loading => ({
   type: SET_LOADING,
   payload: {
     loading
   }
 })
 
-export const setSearch = (search) => ({
+export const setSearch = search => ({
   type: SET_SEARCH,
   payload: {
     search
   }
 })
 
-export const setSession = (session) => ({
+export const setSession = session => ({
   type: SET_SESSION,
   payload: {
     session
   }
 })
 
-export const setError = (error) => ({
+export const setError = error => ({
   type: SET_ERROR,
   payload: {
     error
   }
 })
 
-export const setRoute = (route) => ({
+export const setRoute = route => ({
   type: SET_ROUTE,
   payload: {
     route
diff --git a/openbis_ng_ui/src/store/actions/browser.js b/openbis_ng_ui/src/store/actions/browser.js
index 9bc2e1a2d7de3ca65d138eafa383458dcda3aba9..70d4a875b21436ac97d2d4b72c7d0dc54631e436 100644
--- a/openbis_ng_ui/src/store/actions/browser.js
+++ b/openbis_ng_ui/src/store/actions/browser.js
@@ -12,7 +12,7 @@ export const BROWSER_SET_EXPANDED_NODES = 'BROWSER_SET_EXPANDED_NODES'
 export const BROWSER_ADD_EXPANDED_NODES = 'BROWSER_ADD_EXPANDED_NODES'
 export const BROWSER_REMOVE_EXPANDED_NODES = 'BROWSER_REMOVE_EXPANDED_NODES'
 
-export const browserInit = (page) => ({
+export const browserInit = page => ({
   type: BROWSER_INIT,
   payload: {
     page
diff --git a/openbis_ng_ui/src/store/common/browser.js b/openbis_ng_ui/src/store/common/browser.js
index 31b69b5b9ec235b98460543e5358e31f354ac27b..69d60d6b5bf5a9568e3e51c7c85acb0127941872 100644
--- a/openbis_ng_ui/src/store/common/browser.js
+++ b/openbis_ng_ui/src/store/common/browser.js
@@ -1,24 +1,27 @@
 import _ from 'lodash'
 
-export function mapNodes(parent, nodes, fn){
-  return nodes.map(node => {
-    return fn(parent, node)
-  }).filter(node => {
-    return node !== null
-  }).map(node => {
-    if(node.children){
-      node.children = mapNodes(node, node.children, fn)
-    }
-    return node
-  })
+export function mapNodes(parent, nodes, fn) {
+  return nodes
+    .map(node => {
+      return fn(parent, node)
+    })
+    .filter(node => {
+      return node !== null
+    })
+    .map(node => {
+      if (node.children) {
+        node.children = mapNodes(node, node.children, fn)
+      }
+      return node
+    })
 }
 
-export function getAllNodes(nodes){
+export function getAllNodes(nodes) {
   let levels = getAllNodesByLevel(nodes)
   return _.concat(...levels)
 }
 
-function getAllNodesByLevel(nodes){
+function getAllNodesByLevel(nodes) {
   let levels = []
   let toVisit = []
 
@@ -46,29 +49,32 @@ function getAllNodesByLevel(nodes){
   return levels
 }
 
-export function sortNodes(nodes){
+export function sortNodes(nodes) {
   nodes.sort((n1, n2) => {
     return n1.text.localeCompare(n2.text)
   })
   nodes.forEach(node => {
-    if(node.children){
+    if (node.children) {
       sortNodes(node.children)
     }
   })
 }
 
-export function getMatchingNodes(nodes, matchesFn){
+export function getMatchingNodes(nodes, matchesFn) {
   let allNodes = getAllNodes(nodes)
   let matchingNodes = {}
 
-  let hasMatchingChildren = function(node){
-    return node.children && _.some(node.children, child => {
-      return matchingNodes[child.id]
-    })
+  let hasMatchingChildren = function(node) {
+    return (
+      node.children &&
+      _.some(node.children, child => {
+        return matchingNodes[child.id]
+      })
+    )
   }
 
   allNodes.reverse().forEach(node => {
-    if(hasMatchingChildren(node) || matchesFn(node)){
+    if (hasMatchingChildren(node) || matchesFn(node)) {
       matchingNodes[node.id] = node.id
     }
   })
diff --git a/openbis_ng_ui/src/store/history.js b/openbis_ng_ui/src/store/history.js
index 211bc90d70713b2462e756b406c2e75bcdd73b13..290663fb7799971518ae9246378845ed80c4497f 100644
--- a/openbis_ng_ui/src/store/history.js
+++ b/openbis_ng_ui/src/store/history.js
@@ -3,28 +3,27 @@ import * as actions from './actions/actions.js'
 import routes from '../common/consts/routes.js'
 
 let history = createHashHistory({
-    hashType: 'noslash'
-})  
+  hashType: 'noslash'
+})
 
-history.configure = (store) => {
+history.configure = store => {
+  history.listen(location => {
+    let route = routes.parse(location.pathname)
+    store.dispatch(actions.routeChange(route.path))
+  })
 
-    history.listen((location) => {
-        let route = routes.parse(location.pathname)
-        store.dispatch(actions.routeChange(route.path))
-    })
+  let currentRoute = store.getState().route
 
-    let currentRoute = store.getState().route
+  store.subscribe(() => {
+    let newRoute = store.getState().route
 
-    store.subscribe(() => {
-        let newRoute = store.getState().route
-
-        if(newRoute && newRoute !== currentRoute){
-            currentRoute = newRoute
-            if(currentRoute && currentRoute !== history.location.pathname){
-                history.push(currentRoute)
-            }
-        }
-    })
+    if (newRoute && newRoute !== currentRoute) {
+      currentRoute = newRoute
+      if (currentRoute && currentRoute !== history.location.pathname) {
+        history.push(currentRoute)
+      }
+    }
+  })
 }
 
-export default history
\ No newline at end of file
+export default history
diff --git a/openbis_ng_ui/src/store/middleware/loadingThrottle.js b/openbis_ng_ui/src/store/middleware/loadingThrottle.js
index ca95f9817b92bff1e470be9c786adee2883f152b..08f42053561321d0cac31e8255e891376e551f1c 100644
--- a/openbis_ng_ui/src/store/middleware/loadingThrottle.js
+++ b/openbis_ng_ui/src/store/middleware/loadingThrottle.js
@@ -3,26 +3,27 @@ import * as actions from '../actions/actions.js'
 const MIN_LOADING_TIME = 500
 let startLoadingTimestamp = null
 
-export default () => (next) => (action) => {
-  if(action.type === actions.SET_LOADING){
-    if(action.payload.loading){
-      if(startLoadingTimestamp === null){
+export default () => next => action => {
+  if (action.type === actions.SET_LOADING) {
+    if (action.payload.loading) {
+      if (startLoadingTimestamp === null) {
         startLoadingTimestamp = new Date().getTime()
       }
       next(action)
-    }else{
-      let timeToWait = startLoadingTimestamp + MIN_LOADING_TIME - new Date().getTime()
-      if(timeToWait > 0){
+    } else {
+      let timeToWait =
+        startLoadingTimestamp + MIN_LOADING_TIME - new Date().getTime()
+      if (timeToWait > 0) {
         setTimeout(() => {
           startLoadingTimestamp = null
           next(action)
         }, timeToWait)
-      }else{
+      } else {
         startLoadingTimestamp = null
         next(action)
       }
     }
-  }else{
+  } else {
     next(action)
   }
 }
diff --git a/openbis_ng_ui/src/store/middleware/stateChangeCheck.js b/openbis_ng_ui/src/store/middleware/stateChangeCheck.js
index f6b8bf48e5800d503375bdfc34142f0d4295e0f1..49a0cc07788d6613b9948879b4985e7dbc2bfaa4 100644
--- a/openbis_ng_ui/src/store/middleware/stateChangeCheck.js
+++ b/openbis_ng_ui/src/store/middleware/stateChangeCheck.js
@@ -1,8 +1,8 @@
 import _ from 'lodash'
 import logger from '../../common/logger.js'
 
-export default (store) => (next) => (action) => {
-  if(logger.isLevelEnabled(logger.DEBUG)){
+export default store => next => action => {
+  if (logger.isLevelEnabled(logger.DEBUG)) {
     logger.group(logger.DEBUG, 'Action ' + action.type, action)
 
     let beforeState = store.getState()
@@ -15,53 +15,84 @@ export default (store) => (next) => (action) => {
     let modifiedPreviousState = !_.isEqual(beforeState, beforeStateClone)
     logger.groupEnd(logger.DEBUG)
 
-    if(unmodifiedNewObjects || modifiedPreviousState){
-      if(unmodifiedNewObjects){
-        logger.log(logger.ERROR, 'ERROR state changed incorrectly - returned new objects without changes', beforeState, afterState)
+    if (unmodifiedNewObjects || modifiedPreviousState) {
+      if (unmodifiedNewObjects) {
+        logger.log(
+          logger.ERROR,
+          'ERROR state changed incorrectly - returned new objects without changes',
+          beforeState,
+          afterState
+        )
       }
-      if(modifiedPreviousState){
-        logger.log(logger.ERROR, 'ERROR state changed incorrectly - reducer modified previous state object', beforeStateClone, beforeState, afterState)
+      if (modifiedPreviousState) {
+        logger.log(
+          logger.ERROR,
+          'ERROR state changed incorrectly - reducer modified previous state object',
+          beforeStateClone,
+          beforeState,
+          afterState
+        )
       }
-    }else{
-      if(_.isEqual(beforeState, afterState)){
+    } else {
+      if (_.isEqual(beforeState, afterState)) {
         logger.log(logger.DEBUG, 'OK state not changed', afterState)
-      }else{
-        logger.log(logger.DEBUG, 'OK state changed correctly', beforeState, afterState)
+      } else {
+        logger.log(
+          logger.DEBUG,
+          'OK state changed correctly',
+          beforeState,
+          afterState
+        )
       }
     }
 
     logger.groupEnd(logger.DEBUG)
-  }else{
+  } else {
     next(action)
   }
 }
 
-function diff(beforeState, afterState, path){
+function diff(beforeState, afterState, path) {
   let unmodifiedNewObjects = false
 
-  if(_.isObject(beforeState) && _.isObject(afterState)){
-    if(beforeState === afterState){
+  if (_.isObject(beforeState) && _.isObject(afterState)) {
+    if (beforeState === afterState) {
       logger.log(logger.TRACE, 'OK - ' + path + ' - same object')
-    }else{
-      if(_.isEqual(beforeState, afterState)){
-        logger.log(logger.DEBUG, 'ERROR - ' + path + ' - new object without changes', afterState)
+    } else {
+      if (_.isEqual(beforeState, afterState)) {
+        logger.log(
+          logger.DEBUG,
+          'ERROR - ' + path + ' - new object without changes',
+          afterState
+        )
         unmodifiedNewObjects = true
-      }else{
-        logger.log(logger.DEBUG, 'OK* - ' + path + ' - new object with changes', beforeState, afterState)
+      } else {
+        logger.log(
+          logger.DEBUG,
+          'OK* - ' + path + ' - new object with changes',
+          beforeState,
+          afterState
+        )
       }
 
       let props = _.union(_.keys(beforeState), _.keys(afterState))
       props.forEach(prop => {
         let beforeProp = beforeState ? beforeState[prop] : undefined
         let afterProp = afterState ? afterState[prop] : undefined
-        unmodifiedNewObjects = unmodifiedNewObjects | diff(beforeProp, afterProp, path + '/' + prop)
+        unmodifiedNewObjects =
+          unmodifiedNewObjects | diff(beforeProp, afterProp, path + '/' + prop)
       })
     }
-  }else{
-    if(_.isEqual(beforeState, afterState)){
+  } else {
+    if (_.isEqual(beforeState, afterState)) {
       logger.log(logger.TRACE, 'OK - ' + path + ' - same value', afterState)
-    }else{
-      logger.log(logger.DEBUG, 'OK* - ' + path + ' - changed value', beforeState, afterState)
+    } else {
+      logger.log(
+        logger.DEBUG,
+        'OK* - ' + path + ' - changed value',
+        beforeState,
+        afterState
+      )
     }
   }
 
diff --git a/openbis_ng_ui/src/store/reducers/reducers.js b/openbis_ng_ui/src/store/reducers/reducers.js
index 6ce7ab29fd0601bd1eb2442f1d9027d24e205080..a13a279f0c6384177bfe6730665ab5324b2597ac 100644
--- a/openbis_ng_ui/src/store/reducers/reducers.js
+++ b/openbis_ng_ui/src/store/reducers/reducers.js
@@ -5,7 +5,7 @@ import session from './session/session.js'
 import route from './route/route.js'
 
 export default function root(state = {}, action) {
-  if(action.type === actions.INIT){
+  if (action.type === actions.INIT) {
     state = {}
   }
   return combineReducers({
diff --git a/openbis_ng_ui/src/store/reducers/route/route.js b/openbis_ng_ui/src/store/reducers/route/route.js
index aa02078d9663909db49368c68aabc13fe4e1d606..4218410aeaa9d65f8f4e11a8b1ec7f6df6a22920 100644
--- a/openbis_ng_ui/src/store/reducers/route/route.js
+++ b/openbis_ng_ui/src/store/reducers/route/route.js
@@ -1,6 +1,6 @@
 import * as actions from '../../actions/actions.js'
 
-export default function route(state = null, action){
+export default function route(state = null, action) {
   switch (action.type) {
     case actions.SET_ROUTE: {
       return action.payload.route
diff --git a/openbis_ng_ui/src/store/reducers/session/session.js b/openbis_ng_ui/src/store/reducers/session/session.js
index 9c28ef12190046c1496d7bddf6850513f0c7346a..5a9a3ea08b771d0880969643b1490edc26e08b4d 100644
--- a/openbis_ng_ui/src/store/reducers/session/session.js
+++ b/openbis_ng_ui/src/store/reducers/session/session.js
@@ -1,6 +1,6 @@
 import * as actions from '../../actions/actions.js'
 
-export default function session(state = null, action){
+export default function session(state = null, action) {
   switch (action.type) {
     case actions.SET_SESSION: {
       return action.payload.session
diff --git a/openbis_ng_ui/src/store/reducers/ui/pages/common/browser.js b/openbis_ng_ui/src/store/reducers/ui/pages/common/browser.js
index 1fa5e8ba9545d2be4cf44c837320f851398b70da..f7be907e6f0413b980fe8124a4199bb59863819c 100644
--- a/openbis_ng_ui/src/store/reducers/ui/pages/common/browser.js
+++ b/openbis_ng_ui/src/store/reducers/ui/pages/common/browser.js
@@ -10,12 +10,12 @@ export const browser = combineReducers({
   expandedNodes
 })
 
-function filter(state = '', action){
-  switch(action.type){
+function filter(state = '', action) {
+  switch (action.type) {
     case actions.BROWSER_SET_FILTER:
-      if(_.isEqual(state, action.payload.filter)){
+      if (_.isEqual(state, action.payload.filter)) {
         return state
-      }else{
+      } else {
         return action.payload.filter
       }
     default:
@@ -23,8 +23,8 @@ function filter(state = '', action){
   }
 }
 
-function nodes(state = [], action){
-  switch(action.type){
+function nodes(state = [], action) {
+  switch (action.type) {
     case actions.BROWSER_SET_NODES:
       return action.payload.nodes
     default:
@@ -32,12 +32,12 @@ function nodes(state = [], action){
   }
 }
 
-function selectedNodes(state = [], action){
-  switch(action.type){
+function selectedNodes(state = [], action) {
+  switch (action.type) {
     case actions.BROWSER_SET_SELECTED_NODES:
-      if(_.isEqual(state, action.payload.ids)){
+      if (_.isEqual(state, action.payload.ids)) {
         return state
-      }else{
+      } else {
         return action.payload.ids
       }
     default:
@@ -45,43 +45,43 @@ function selectedNodes(state = [], action){
   }
 }
 
-function visibleNodes(state = [], action){
-  switch(action.type){
-    case actions.BROWSER_SET_VISIBLE_NODES:{
-      if(_.isEqual(state, action.payload.ids)){
+function visibleNodes(state = [], action) {
+  switch (action.type) {
+    case actions.BROWSER_SET_VISIBLE_NODES: {
+      if (_.isEqual(state, action.payload.ids)) {
         return state
       } else {
         return action.payload.ids
       }
     }
-    default:{
+    default: {
       return state
     }
   }
 }
 
-function expandedNodes(state = [], action){
+function expandedNodes(state = [], action) {
   let newState = null
 
-  switch(action.type){
-    case actions.BROWSER_SET_EXPANDED_NODES:{
+  switch (action.type) {
+    case actions.BROWSER_SET_EXPANDED_NODES: {
       newState = action.payload.ids
       break
     }
-    case actions.BROWSER_ADD_EXPANDED_NODES:{
+    case actions.BROWSER_ADD_EXPANDED_NODES: {
       newState = _.union(state, action.payload.ids)
       break
     }
-    case actions.BROWSER_REMOVE_EXPANDED_NODES:{
+    case actions.BROWSER_REMOVE_EXPANDED_NODES: {
       newState = _.without(state, ...action.payload.ids)
       break
     }
-    default:{
+    default: {
       return state
     }
   }
 
-  if(_.isEqual(state, newState)){
+  if (_.isEqual(state, newState)) {
     return state
   } else {
     return newState
diff --git a/openbis_ng_ui/src/store/reducers/ui/pages/common/page.js b/openbis_ng_ui/src/store/reducers/ui/pages/common/page.js
index 0a6bc47eacc27566f9749f0833a971b4cda83c65..3b07945d25f4938d93f8523fb743388fac3be5c6 100644
--- a/openbis_ng_ui/src/store/reducers/ui/pages/common/page.js
+++ b/openbis_ng_ui/src/store/reducers/ui/pages/common/page.js
@@ -3,16 +3,19 @@ import * as actions from '../../../../actions/actions.js'
 
 export * from './browser.js'
 
-export function isPageAction(page, action){
-  return action.type === actions.INIT || page === (action.payload && action.payload.page)
+export function isPageAction(page, action) {
+  return (
+    action.type === actions.INIT ||
+    page === (action.payload && action.payload.page)
+  )
 }
 
 export const currentRoute = (state = null, action) => {
-  switch(action.type){
-    case actions.SET_CURRENT_ROUTE:{
+  switch (action.type) {
+    case actions.SET_CURRENT_ROUTE: {
       return action.payload.currentRoute
     }
-    default:{
+    default: {
       return state
     }
   }
@@ -21,21 +24,29 @@ export const currentRoute = (state = null, action) => {
 export const openObjects = (state = [], action) => {
   let newState = null
 
-  switch(action.type){
-    case actions.ADD_OPEN_OBJECT:{
-      newState = _.unionWith(state, [{type: action.payload.type, id: action.payload.id}], _.isEqual)
+  switch (action.type) {
+    case actions.ADD_OPEN_OBJECT: {
+      newState = _.unionWith(
+        state,
+        [{ type: action.payload.type, id: action.payload.id }],
+        _.isEqual
+      )
       break
     }
-    case actions.REMOVE_OPEN_OBJECT:{
-      newState = _.differenceWith(state, [{type: action.payload.type, id: action.payload.id}], _.isEqual)
+    case actions.REMOVE_OPEN_OBJECT: {
+      newState = _.differenceWith(
+        state,
+        [{ type: action.payload.type, id: action.payload.id }],
+        _.isEqual
+      )
       break
     }
-    default:{
+    default: {
       return state
     }
   }
 
-  if(_.isEqual(state, newState)){
+  if (_.isEqual(state, newState)) {
     return state
   } else {
     return newState
@@ -45,21 +56,29 @@ export const openObjects = (state = [], action) => {
 export const changedObjects = (state = [], action) => {
   let newState = null
 
-  switch(action.type){
-    case actions.ADD_CHANGED_OBJECT:{
-      newState = _.unionWith(state, [{type: action.payload.type, id: action.payload.id}], _.isEqual)
+  switch (action.type) {
+    case actions.ADD_CHANGED_OBJECT: {
+      newState = _.unionWith(
+        state,
+        [{ type: action.payload.type, id: action.payload.id }],
+        _.isEqual
+      )
       break
     }
-    case actions.REMOVE_CHANGED_OBJECT:{
-      newState = _.differenceWith(state, [{type: action.payload.type, id: action.payload.id}], _.isEqual)
+    case actions.REMOVE_CHANGED_OBJECT: {
+      newState = _.differenceWith(
+        state,
+        [{ type: action.payload.type, id: action.payload.id }],
+        _.isEqual
+      )
       break
     }
-    default:{
+    default: {
       return state
     }
   }
 
-  if(_.isEqual(state, newState)){
+  if (_.isEqual(state, newState)) {
     return state
   } else {
     return newState
diff --git a/openbis_ng_ui/src/store/reducers/ui/pages/login/login.js b/openbis_ng_ui/src/store/reducers/ui/pages/login/login.js
index fcc72a8d641c3f67d1f01482812784c5655021a6..9780c6f45516aeb29ba87c530072a5a22e0e2881 100644
--- a/openbis_ng_ui/src/store/reducers/ui/pages/login/login.js
+++ b/openbis_ng_ui/src/store/reducers/ui/pages/login/login.js
@@ -1,3 +1,3 @@
-export default function login(state = {}){
+export default function login(state = {}) {
   return state
 }
diff --git a/openbis_ng_ui/src/store/reducers/ui/pages/types/types.js b/openbis_ng_ui/src/store/reducers/ui/pages/types/types.js
index dac9df43a704b6315bd76b21b498e138a0ea4a1f..86af2ada258dc284aadd1e35ad9b75eaa70e54e0 100644
--- a/openbis_ng_ui/src/store/reducers/ui/pages/types/types.js
+++ b/openbis_ng_ui/src/store/reducers/ui/pages/types/types.js
@@ -2,15 +2,15 @@ import { combineReducers } from 'redux'
 import * as pages from '../../../../../common/consts/pages.js'
 import * as page from '../common/page.js'
 
-export default function types(state = {}, action){
-  if(page.isPageAction(pages.TYPES, action)){
+export default function types(state = {}, action) {
+  if (page.isPageAction(pages.TYPES, action)) {
     return combineReducers({
       currentRoute: page.currentRoute,
       browser: page.browser,
       openObjects: page.openObjects,
       changedObjects: page.changedObjects
     })(state, action)
-  }else{
+  } else {
     return state
   }
 }
diff --git a/openbis_ng_ui/src/store/reducers/ui/pages/users/users.js b/openbis_ng_ui/src/store/reducers/ui/pages/users/users.js
index cc1ab4ef496bc0f211f4d99dcda4e19e1291c37b..3213985686178ea5c58335c7b1ecad0f1a3b7acd 100644
--- a/openbis_ng_ui/src/store/reducers/ui/pages/users/users.js
+++ b/openbis_ng_ui/src/store/reducers/ui/pages/users/users.js
@@ -2,15 +2,15 @@ import { combineReducers } from 'redux'
 import * as pages from '../../../../../common/consts/pages.js'
 import * as page from '../common/page.js'
 
-export default function types(state = {}, action){
-  if(page.isPageAction(pages.USERS, action)){
+export default function types(state = {}, action) {
+  if (page.isPageAction(pages.USERS, action)) {
     return combineReducers({
       currentRoute: page.currentRoute,
       browser: page.browser,
       openObjects: page.openObjects,
       changedObjects: page.changedObjects
     })(state, action)
-  }else{
+  } else {
     return state
   }
 }
diff --git a/openbis_ng_ui/src/store/reducers/ui/ui.js b/openbis_ng_ui/src/store/reducers/ui/ui.js
index 1ea3cc7803e6ea3e4a35eb36ef79dc9114fbd655..e86b2eb900a337193121d80f51c0dac53928a3a8 100644
--- a/openbis_ng_ui/src/store/reducers/ui/ui.js
+++ b/openbis_ng_ui/src/store/reducers/ui/ui.js
@@ -9,7 +9,7 @@ export default combineReducers({
   error
 })
 
-function loading(state = false, action){
+function loading(state = false, action) {
   switch (action.type) {
     case actions.SET_LOADING: {
       return action.payload.loading
@@ -20,7 +20,7 @@ function loading(state = false, action){
   }
 }
 
-function search(state = '', action){
+function search(state = '', action) {
   switch (action.type) {
     case actions.SET_SEARCH: {
       return action.payload.search
@@ -31,7 +31,7 @@ function search(state = '', action){
   }
 }
 
-function error(state = null, action){
+function error(state = null, action) {
   switch (action.type) {
     case actions.SET_ERROR: {
       return action.payload.error
diff --git a/openbis_ng_ui/src/store/sagas/api.js b/openbis_ng_ui/src/store/sagas/api.js
index e1cf527237829b6ddf414723d084599bc5e9530f..62c4b97b18a60a95c2c63ac2496bf1ed7d9d95ec 100644
--- a/openbis_ng_ui/src/store/sagas/api.js
+++ b/openbis_ng_ui/src/store/sagas/api.js
@@ -1,18 +1,28 @@
-import {put, takeEvery, apply} from 'redux-saga/effects'
-import {facade} from '../../services/openbis.js'
+import { put, takeEvery, apply } from 'redux-saga/effects'
+import { facade } from '../../services/openbis.js'
 import * as actions from '../actions/actions.js'
 
 export default function* apiSaga() {
   yield takeEvery(actions.API_REQUEST, apiRequest)
 }
 
-function* apiRequest(action){
+function* apiRequest(action) {
   const { method, params } = action.payload
 
-  try{
+  try {
     let result = yield apply(facade, facade[method], params || [])
-    yield put(actions.apiSuccess({result, meta: Object.assign({}, action.meta, {method, params})}))
-  }catch(error){
-    yield put(actions.apiError({error, meta: Object.assign({}, action.meta, {method, params})}))
+    yield put(
+      actions.apiSuccess({
+        result,
+        meta: Object.assign({}, action.meta, { method, params })
+      })
+    )
+  } catch (error) {
+    yield put(
+      actions.apiError({
+        error,
+        meta: Object.assign({}, action.meta, { method, params })
+      })
+    )
   }
 }
diff --git a/openbis_ng_ui/src/store/sagas/app.js b/openbis_ng_ui/src/store/sagas/app.js
index 60d368fbd63798d0b566b5a4a2e92a8a7fae6b2f..328d1306929499241049519a77c0ce5bcdab6dcf 100644
--- a/openbis_ng_ui/src/store/sagas/app.js
+++ b/openbis_ng_ui/src/store/sagas/app.js
@@ -1,5 +1,5 @@
-import {call, put, putAndWait, takeEvery, select} from './effects.js'
-import {facade, dto} from '../../services/openbis.js'
+import { call, put, putAndWait, takeEvery, select } from './effects.js'
+import { facade, dto } from '../../services/openbis.js'
 import * as selectors from '../selectors/selectors.js'
 import * as actions from '../actions/actions.js'
 import * as objectTypes from '../../common/consts/objectType.js'
@@ -17,83 +17,89 @@ export default function* appSaga() {
 }
 
 function* init() {
-  try{
+  try {
     yield put(actions.setLoading(true))
     yield call([dto, dto.init])
     yield call([facade, facade.init])
-  }catch(e){
+  } catch (e) {
     yield put(actions.setError(e))
-  }finally{
+  } finally {
     yield put(actions.setLoading(false))
   }
 }
 
 function* login(action) {
-  try{
+  try {
     yield put(actions.setLoading(true))
 
     let { username, password } = action.payload
-    let loginResponse = yield putAndWait(actions.apiRequest({method: 'login', params: [ username, password ]}))
+    let loginResponse = yield putAndWait(
+      actions.apiRequest({ method: 'login', params: [username, password] })
+    )
 
-    if(loginResponse.payload.result){
-      yield put(actions.setSession({
-        sessionToken: loginResponse.payload.result,
-        userName: username
-      }))
+    if (loginResponse.payload.result) {
+      yield put(
+        actions.setSession({
+          sessionToken: loginResponse.payload.result,
+          userName: username
+        })
+      )
 
       let path = yield select(selectors.getRoute)
       let route = routes.parse(path)
       yield put(actions.routeChange(route.path))
-    }else{
+    } else {
       throw { message: 'Incorrect used or password' }
     }
-  }catch(e){
+  } catch (e) {
     yield put(actions.setError(e))
-  }finally{
+  } finally {
     yield put(actions.setLoading(false))
   }
 }
 
 function* logout() {
-  try{
+  try {
     yield put(actions.setLoading(true))
-    yield putAndWait(actions.apiRequest({method: 'logout'}))
+    yield putAndWait(actions.apiRequest({ method: 'logout' }))
     yield put(actions.init())
     yield put(actions.routeChange('/'))
-  }catch(e){
+  } catch (e) {
     yield put(actions.setError(e))
-  }finally{
+  } finally {
     yield put(actions.setLoading(false))
   }
 }
 
 function* search(action) {
-  const {page, text} = action.payload
-  yield put(actions.objectOpen(page, objectTypes.SEARCH, text.trim()))
+  const { page, text } = action.payload
+  if (text.trim().length > 0) {
+    yield put(actions.objectOpen(page, objectTypes.SEARCH, text.trim()))
+  }
   yield put(actions.setSearch(''))
 }
 
-function* currentPageChange(action){
+function* currentPageChange(action) {
   let page = action.payload.currentPage
   let route = yield select(selectors.getCurrentRoute, page)
 
-  if(route){
+  if (route) {
     yield put(actions.routeChange(route))
-  }else{
+  } else {
     route = routes.format({ page })
     yield put(actions.routeChange(route))
   }
 }
 
-function* searchChange(action){
+function* searchChange(action) {
   yield put(actions.setSearch(action.payload.search))
 }
 
-function* errorChange(action){
+function* errorChange(action) {
   yield put(actions.setError(action.payload.error))
 }
 
-function* routeChange(action){
+function* routeChange(action) {
   const route = action.payload.route
   yield put(actions.setRoute(route))
 }
diff --git a/openbis_ng_ui/src/store/sagas/browser/browser.js b/openbis_ng_ui/src/store/sagas/browser/browser.js
index fb209d7df413ff0f239ec14c178da987ba6414dc..15794e867d5a562911c56b5a6e291bd95f9ad494 100644
--- a/openbis_ng_ui/src/store/sagas/browser/browser.js
+++ b/openbis_ng_ui/src/store/sagas/browser/browser.js
@@ -1,5 +1,5 @@
 import _ from 'lodash'
-import {put, takeEvery, select} from './../effects.js'
+import { put, takeEvery, select } from './../effects.js'
 
 import * as selectors from '../../selectors/selectors.js'
 import * as actions from '../../actions/actions.js'
@@ -31,7 +31,7 @@ function* browserInit(action) {
   yield put(actions.browserSetVisibleNodes(page, filteredNodes))
 }
 
-function* browserFilterChange(action){
+function* browserFilterChange(action) {
   let page = action.payload.page
   let filter = action.payload.filter
 
@@ -39,26 +39,29 @@ function* browserFilterChange(action){
   let filteredNodes = new Set(browserFilter(allNodes, filter))
   let expandedNodes = []
 
-  if(filter && filter.trim()){
+  if (filter && filter.trim()) {
     common.mapNodes(null, allNodes, (parent, node) => {
-      if(_.size(node.children) > 0 && _.some(node.children, child => {
-        return filteredNodes.has(child.id)
-      })){
+      if (
+        _.size(node.children) > 0 &&
+        _.some(node.children, child => {
+          return filteredNodes.has(child.id)
+        })
+      ) {
         expandedNodes.push(node.id)
       }
       return node
     })
   }
 
-  if(_.size(expandedNodes) > 0){
+  if (_.size(expandedNodes) > 0) {
     yield put(actions.browserAddExpandedNodes(page, expandedNodes))
   }
   yield put(actions.browserSetVisibleNodes(page, filteredNodes))
   yield put(actions.browserSetFilter(page, action.payload.filter))
 }
 
-function* browserNodeSelect(action){
-  let {page, id} = action.payload
+function* browserNodeSelect(action) {
+  let { page, id } = action.payload
   let allNodes = yield select(selectors.getAllBrowserNodes, page)
   let allNodesAllLevels = common.getAllNodes(allNodes)
 
@@ -66,59 +69,73 @@ function* browserNodeSelect(action){
     return node.id === id
   })
 
-  if(nodeWithId && nodeWithId.object){
-    let idsToSelect = _.reduce(allNodesAllLevels, (array, node) => {
-      if(_.isEqual(nodeWithId.object, node.object)){
-        array.push(node.id)
-      }
-      return array
-    }, [])
+  if (nodeWithId && nodeWithId.object) {
+    let idsToSelect = _.reduce(
+      allNodesAllLevels,
+      (array, node) => {
+        if (_.isEqual(nodeWithId.object, node.object)) {
+          array.push(node.id)
+        }
+        return array
+      },
+      []
+    )
     yield put(actions.browserSetSelectedNodes(page, idsToSelect))
-    yield put(actions.objectOpen(page, nodeWithId.object.type, nodeWithId.object.id))
-  }else{
+    yield put(
+      actions.objectOpen(page, nodeWithId.object.type, nodeWithId.object.id)
+    )
+  } else {
     let idsToSelect = nodeWithId ? [nodeWithId.id] : []
     yield put(actions.browserSetSelectedNodes(page, idsToSelect))
   }
 }
 
-function* browserNodeExpand(action){
-  yield put(actions.browserAddExpandedNodes(action.payload.page, [action.payload.id]))
+function* browserNodeExpand(action) {
+  yield put(
+    actions.browserAddExpandedNodes(action.payload.page, [action.payload.id])
+  )
 }
 
-function* browserNodeCollapse(action){
-  yield put(actions.browserRemoveExpandedNodes(action.payload.page, [action.payload.id]))
+function* browserNodeCollapse(action) {
+  yield put(
+    actions.browserRemoveExpandedNodes(action.payload.page, [action.payload.id])
+  )
 }
 
-function browserFilter(nodes, filter){
-  filter = filter && filter.trim().toLowerCase() || ''
+function browserFilter(nodes, filter) {
+  filter = (filter && filter.trim().toLowerCase()) || ''
   return common.getMatchingNodes(nodes, node => {
     return node.text && node.text.toLowerCase().indexOf(filter) !== -1
   })
 }
 
-function* routeChange(action){
+function* routeChange(action) {
   let route = routes.parse(action.payload.route)
   let browser = yield select(selectors.getBrowser, route.page)
 
-  if(browser){
+  if (browser) {
     let selectedObject = { type: route.type, id: route.id }
     let allNodes = yield select(selectors.getAllBrowserNodes, route.page)
     let allNodesAllLevels = common.getAllNodes(allNodes)
 
-    let idsToSelect = _.reduce(allNodesAllLevels, (array, node) => {
-      if(_.isEqual(selectedObject, node.object)){
-        array.push(node.id)
-      }
-      return array
-    }, [])
+    let idsToSelect = _.reduce(
+      allNodesAllLevels,
+      (array, node) => {
+        if (_.isEqual(selectedObject, node.object)) {
+          array.push(node.id)
+        }
+        return array
+      },
+      []
+    )
 
     yield put(actions.browserSetSelectedNodes(route.page, idsToSelect))
   }
 }
 
-function getBrowserImpl(page){
-  switch(page){
-    case pages.TYPES :
+function getBrowserImpl(page) {
+  switch (page) {
+    case pages.TYPES:
       return typesBrowser
     case pages.USERS:
       return usersBrowser
diff --git a/openbis_ng_ui/src/store/sagas/browser/types.js b/openbis_ng_ui/src/store/sagas/browser/types.js
index dc23f3afdb989e8dbb2884eee6755e2dd717c1ff..64618235b68d071b8d8c1246af384a63b19f15a4 100644
--- a/openbis_ng_ui/src/store/sagas/browser/types.js
+++ b/openbis_ng_ui/src/store/sagas/browser/types.js
@@ -1,19 +1,24 @@
 import _ from 'lodash'
-import {putAndWait} from './../effects.js'
-import {dto} from '../../../services/openbis.js'
+import { putAndWait } from './../effects.js'
+import { dto } from '../../../services/openbis.js'
 import * as objectType from '../../../common/consts/objectType.js'
 import * as actions from '../../actions/actions.js'
 import * as common from '../../common/browser.js'
 
 export function* createNodes() {
-  let {objectTypes, collectionTypes, dataSetTypes, materialTypes} = yield getTypes()
+  let {
+    objectTypes,
+    collectionTypes,
+    dataSetTypes,
+    materialTypes
+  } = yield getTypes()
 
-  let convert = function(types, typeName){
+  let convert = function(types, typeName) {
     return _.map(types, type => {
       return {
         id: `${typeName}s/${type.code}`,
         text: type.code,
-        object: {type: typeName, id: type.code}
+        object: { type: typeName, id: type.code }
       }
     })
   }
@@ -28,48 +33,65 @@ export function* createNodes() {
   common.sortNodes(dataSetTypeNodes)
   common.sortNodes(materialTypeNodes)
 
-  let nodes = [{
-    id: 'objectTypes',
-    text: 'Object Types',
-    children: objectTypeNodes
-  }, {
-    id: 'collectionTypes',
-    text: 'Collection Types',
-    children: collectionTypeNodes
-  },{
-    id: 'dataSetTypes',
-    text: 'Data Set Types',
-    children: dataSetTypeNodes
-  },{
-    id: 'materialTypes',
-    text: 'Material Types',
-    children: materialTypeNodes
-  }]
+  let nodes = [
+    {
+      id: 'objectTypes',
+      text: 'Object Types',
+      children: objectTypeNodes
+    },
+    {
+      id: 'collectionTypes',
+      text: 'Collection Types',
+      children: collectionTypeNodes
+    },
+    {
+      id: 'dataSetTypes',
+      text: 'Data Set Types',
+      children: dataSetTypeNodes
+    },
+    {
+      id: 'materialTypes',
+      text: 'Material Types',
+      children: materialTypeNodes
+    }
+  ]
 
   return nodes
 }
 
-function* getTypes(){
+function* getTypes() {
   let responses = yield putAndWait({
     objectTypes: actions.apiRequest({
       method: 'searchSampleTypes',
-      params: [new dto.SampleTypeSearchCriteria(), new dto.SampleTypeFetchOptions()]
+      params: [
+        new dto.SampleTypeSearchCriteria(),
+        new dto.SampleTypeFetchOptions()
+      ]
     }),
     collectionTypes: actions.apiRequest({
       method: 'searchExperimentTypes',
-      params: [new dto.ExperimentTypeSearchCriteria(), new dto.ExperimentTypeFetchOptions()]
+      params: [
+        new dto.ExperimentTypeSearchCriteria(),
+        new dto.ExperimentTypeFetchOptions()
+      ]
     }),
     dataSetTypes: actions.apiRequest({
       method: 'searchDataSetTypes',
-      params: [new dto.DataSetTypeSearchCriteria(), new dto.DataSetTypeFetchOptions()]
+      params: [
+        new dto.DataSetTypeSearchCriteria(),
+        new dto.DataSetTypeFetchOptions()
+      ]
     }),
     materialTypes: actions.apiRequest({
       method: 'searchMaterialTypes',
-      params: [new dto.MaterialTypeSearchCriteria(), new dto.MaterialTypeFetchOptions()]
+      params: [
+        new dto.MaterialTypeSearchCriteria(),
+        new dto.MaterialTypeFetchOptions()
+      ]
     })
   })
 
-  let convert = function(response){
+  let convert = function(response) {
     return response.payload.result.getObjects().map(type => {
       return {
         code: type.getCode(),
diff --git a/openbis_ng_ui/src/store/sagas/browser/users.js b/openbis_ng_ui/src/store/sagas/browser/users.js
index f81deccee23883bab860505fb8d18e9d9f2a884d..265b6fc3c57c8f94ef0036d418dbb07880ff226b 100644
--- a/openbis_ng_ui/src/store/sagas/browser/users.js
+++ b/openbis_ng_ui/src/store/sagas/browser/users.js
@@ -1,12 +1,12 @@
 import _ from 'lodash'
-import {putAndWait} from './../effects.js'
-import {dto} from '../../../services/openbis.js'
+import { putAndWait } from './../effects.js'
+import { dto } from '../../../services/openbis.js'
 import * as objectType from '../../../common/consts/objectType.js'
 import * as actions from '../../actions/actions.js'
 import * as common from '../../common/browser.js'
 
 export function* createNodes() {
-  let {users, groups} = yield getUsersAndGroups()
+  let { users, groups } = yield getUsersAndGroups()
 
   let userNodes = _.map(users, user => {
     return {
@@ -44,32 +44,39 @@ export function* createNodes() {
 
   common.sortNodes(groupNodes)
 
-  let nodes = [{
-    id: 'users',
-    text: 'Users',
-    children: userNodes
-  }, {
-    id: 'groups',
-    text: 'Groups',
-    children: groupNodes
-  }]
+  let nodes = [
+    {
+      id: 'users',
+      text: 'Users',
+      children: userNodes
+    },
+    {
+      id: 'groups',
+      text: 'Groups',
+      children: groupNodes
+    }
+  ]
 
   return nodes
 }
 
-function* getUsersAndGroups(){
-  let getUsersReponse = yield putAndWait(actions.apiRequest({
-    method: 'searchPersons',
-    params: [new dto.PersonSearchCriteria(), new dto.PersonFetchOptions()]
-  }))
+function* getUsersAndGroups() {
+  let getUsersReponse = yield putAndWait(
+    actions.apiRequest({
+      method: 'searchPersons',
+      params: [new dto.PersonSearchCriteria(), new dto.PersonFetchOptions()]
+    })
+  )
 
   let groupFetchOptions = new dto.AuthorizationGroupFetchOptions()
   groupFetchOptions.withUsers()
 
-  let getGroupsReponse = yield putAndWait(actions.apiRequest({
-    method: 'searchAuthorizationGroups',
-    params: [new dto.AuthorizationGroupSearchCriteria(), groupFetchOptions]
-  }))
+  let getGroupsReponse = yield putAndWait(
+    actions.apiRequest({
+      method: 'searchAuthorizationGroups',
+      params: [new dto.AuthorizationGroupSearchCriteria(), groupFetchOptions]
+    })
+  )
 
   let users = {}
   let groups = {}
@@ -88,7 +95,7 @@ function* getUsersAndGroups(){
       code: group.code,
       userIds: group.users.reduce((groupUserIds, groupUser) => {
         let user = users[groupUser.userId]
-        if(user){
+        if (user) {
           user.groupIds.push(group.code)
           groupUserIds.push(user.userId)
         }
@@ -97,5 +104,5 @@ function* getUsersAndGroups(){
     }
   })
 
-  return {users, groups}
+  return { users, groups }
 }
diff --git a/openbis_ng_ui/src/store/sagas/effects.js b/openbis_ng_ui/src/store/sagas/effects.js
index b61930b7ef88929d50ff18f3e57e6372a6a8a151..783b763f1094e76fd57abecff831e895a92e0f7b 100644
--- a/openbis_ng_ui/src/store/sagas/effects.js
+++ b/openbis_ng_ui/src/store/sagas/effects.js
@@ -1,15 +1,18 @@
 import _ from 'lodash'
-import {put, take} from 'redux-saga/effects'
+import { put, take } from 'redux-saga/effects'
 
-function generateCorrelationId(){
+function generateCorrelationId() {
   let date = new Date()
-  let timestamp = date.getUTCHours() + ':' + date.getUTCMinutes() + ':' + date.getUTCSeconds()
+  let timestamp =
+    date.getUTCHours() + ':' + date.getUTCMinutes() + ':' + date.getUTCSeconds()
   let random = Math.floor(Math.random() * 1000000)
   return timestamp + '-' + random
 }
 
-export function* putAndWait(actionOrActionsMap){
-  let actionsMap = _.isString(actionOrActionsMap.type) ? {action: actionOrActionsMap} : actionOrActionsMap
+export function* putAndWait(actionOrActionsMap) {
+  let actionsMap = _.isString(actionOrActionsMap.type)
+    ? { action: actionOrActionsMap }
+    : actionOrActionsMap
 
   let correlationId = generateCorrelationId()
   let correlationKeys = []
@@ -20,19 +23,23 @@ export function* putAndWait(actionOrActionsMap){
     actions.push(action)
   })
 
-  for(let i = 0; i < actions.length; i++){
+  for (let i = 0; i < actions.length; i++) {
     let actionWithCorrelation = Object.assign(actions[i], {
-      meta: { correlation: { id: correlationId, key: correlationKeys[i] }}
+      meta: { correlation: { id: correlationId, key: correlationKeys[i] } }
     })
     yield put(actionWithCorrelation)
   }
 
   let responsesMap = {}
 
-  while(_.size(responsesMap) < correlationKeys.length){
+  while (_.size(responsesMap) < correlationKeys.length) {
     let potentialResponse = yield take('*')
-    if(potentialResponse.meta && potentialResponse.meta.correlation && potentialResponse.meta.correlation.id === correlationId){
-      if(potentialResponse.payload.error){
+    if (
+      potentialResponse.meta &&
+      potentialResponse.meta.correlation &&
+      potentialResponse.meta.correlation.id === correlationId
+    ) {
+      if (potentialResponse.payload.error) {
         throw potentialResponse.payload.error
       }
       let correlationKey = potentialResponse.meta.correlation.key
@@ -40,7 +47,9 @@ export function* putAndWait(actionOrActionsMap){
     }
   }
 
-  return _.isString(actionOrActionsMap.type) ? responsesMap.action : responsesMap
+  return _.isString(actionOrActionsMap.type)
+    ? responsesMap.action
+    : responsesMap
 }
 
 export * from 'redux-saga/effects'
diff --git a/openbis_ng_ui/src/store/sagas/page.js b/openbis_ng_ui/src/store/sagas/page.js
index 61a9b07ce61d86d3fa27519a36319f9897d90f9c..72ff8997676bc7b259be6ef073373977e466b2a4 100644
--- a/openbis_ng_ui/src/store/sagas/page.js
+++ b/openbis_ng_ui/src/store/sagas/page.js
@@ -1,5 +1,5 @@
 import _ from 'lodash'
-import {put, takeEvery, select} from './effects.js'
+import { put, takeEvery, select } from './effects.js'
 import * as selectors from '../selectors/selectors.js'
 import * as actions from '../actions/actions.js'
 import routes from '../../common/consts/routes.js'
@@ -22,9 +22,9 @@ function* objectOpen(action) {
 function* objectChange(action) {
   let { page, type, id, changed } = action.payload
 
-  if(changed){
+  if (changed) {
     yield put(actions.addChangedObject(page, type, id))
-  }else{
+  } else {
     yield put(actions.removeChangedObject(page, type, id))
   }
 }
@@ -35,14 +35,18 @@ function* objectClose(action) {
   let selectedObject = yield select(getSelectedObject, page)
   let openObjects = yield select(selectors.getOpenObjects, page)
 
-  if(selectedObject && selectedObject.type === type && selectedObject.id === id){
-    if(_.size(openObjects) === 1){
+  if (
+    selectedObject &&
+    selectedObject.type === type &&
+    selectedObject.id === id
+  ) {
+    if (_.size(openObjects) === 1) {
       selectedObject = null
-    }else{
+    } else {
       let selectedIndex = _.findIndex(openObjects, selectedObject)
-      if(selectedIndex === 0){
+      if (selectedIndex === 0) {
         selectedObject = openObjects[selectedIndex + 1]
-      }else{
+      } else {
         selectedObject = openObjects[selectedIndex - 1]
       }
     }
@@ -51,23 +55,27 @@ function* objectClose(action) {
   yield put(actions.removeOpenObject(page, type, id))
   yield put(actions.removeChangedObject(page, type, id))
 
-  if(selectedObject){
-    let route = routes.format({ page, type: selectedObject.type, id: selectedObject.id })
+  if (selectedObject) {
+    let route = routes.format({
+      page,
+      type: selectedObject.type,
+      id: selectedObject.id
+    })
     yield put(actions.routeChange(route))
-  }else{
+  } else {
     let route = routes.format({ page })
     yield put(actions.routeChange(route))
   }
 }
 
-function* routeChange(action){
+function* routeChange(action) {
   let route = routes.parse(action.payload.route)
 
-  if(route.type && route.id){
+  if (route.type && route.id) {
     let openObjects = yield select(selectors.getOpenObjects, route.page)
     let selectedObject = { type: route.type, id: route.id }
 
-    if(_.findIndex(openObjects, selectedObject) === -1){
+    if (_.findIndex(openObjects, selectedObject) === -1) {
       yield put(actions.addOpenObject(route.page, route.type, route.id))
     }
   }
diff --git a/openbis_ng_ui/src/store/sagas/sagas.js b/openbis_ng_ui/src/store/sagas/sagas.js
index 7bc02f20265a2f4b9ded3f5c668d8f2af66557b2..e7e6363f978643533d1da74cef346542450897e2 100644
--- a/openbis_ng_ui/src/store/sagas/sagas.js
+++ b/openbis_ng_ui/src/store/sagas/sagas.js
@@ -1,14 +1,9 @@
-import {all} from 'redux-saga/effects'
+import { all } from 'redux-saga/effects'
 import app from './app.js'
 import api from './api.js'
 import page from './page.js'
 import browser from './browser/browser.js'
 
 export default function* root() {
-  yield all([
-    api(),
-    app(),
-    page(),
-    browser()
-  ])
+  yield all([api(), app(), page(), browser()])
 }
diff --git a/openbis_ng_ui/src/store/selectors/app.js b/openbis_ng_ui/src/store/selectors/app.js
index 7814cdb1457b768314ac9521bfdf6d40fe033068..23cdfafef467958a274b45d2ea9f75aa195d5767 100644
--- a/openbis_ng_ui/src/store/selectors/app.js
+++ b/openbis_ng_ui/src/store/selectors/app.js
@@ -1,26 +1,26 @@
 import routes from '../../common/consts/routes.js'
 
-export const getLoading = (state) => {
+export const getLoading = state => {
   return state.ui.loading
 }
 
-export const getSearch = (state) => {
+export const getSearch = state => {
   return state.ui.search
 }
 
-export const getRoute = (state) => {
+export const getRoute = state => {
   return state.route
 }
 
-export const getCurrentPage = (state) => {
+export const getCurrentPage = state => {
   let route = routes.parse(state.route)
   return route.page
 }
 
-export const getError = (state) => {
+export const getError = state => {
   return state.ui.error
 }
 
-export const getSession = (state) => {
+export const getSession = state => {
   return state.session
 }
diff --git a/openbis_ng_ui/src/store/selectors/browser.js b/openbis_ng_ui/src/store/selectors/browser.js
index 71e1b19b0b80726e3bd138e1dcaf5af63ec35c8e..702e51331fa081cd44fd8e2fe3c816327cd287c3 100644
--- a/openbis_ng_ui/src/store/selectors/browser.js
+++ b/openbis_ng_ui/src/store/selectors/browser.js
@@ -35,17 +35,17 @@ export const createGetBrowserNodes = () => {
       let nodes = browser.nodes
 
       nodes = common.mapNodes(null, nodes, (parent, node) => {
-        if(visibleNodes.has(node.id)){
+        if (visibleNodes.has(node.id)) {
           let selected = selectedNodes.has(node.id)
           let expanded = expandedNodes.has(node.id)
           return Object.assign({ selected, expanded }, node)
-        }else{
+        } else {
           return null
         }
       })
 
       nodes = common.mapNodes(null, nodes, (parent, node) => {
-        if(_.size(node.children) === 0){
+        if (_.size(node.children) === 0) {
           delete node['children']
         }
         return node
diff --git a/openbis_ng_ui/src/store/selectors/page.js b/openbis_ng_ui/src/store/selectors/page.js
index e8a31f0a8f0b6e621a4d5a73307340a0d217da9f..6318cbba7549c16e3494449ba7a2a8f9ebda4cfa 100644
--- a/openbis_ng_ui/src/store/selectors/page.js
+++ b/openbis_ng_ui/src/store/selectors/page.js
@@ -22,9 +22,9 @@ export const createGetSelectedObject = () => {
     [getCurrentRoute],
     path => {
       logger.log(logger.DEBUG, 'pageSelector.getSelectedObject')
-      if(path){
+      if (path) {
         let route = routes.parse(path)
-        if(route && route.type && route.id){
+        if (route && route.type && route.id) {
           return { type: route.type, id: route.id }
         }
       }
diff --git a/openbis_ng_ui/src/store/store.js b/openbis_ng_ui/src/store/store.js
index bdf32e07bbb2ae9be8220b6ea4d6cd2313b36614..91f70997aec01d8245fb794b858bd1033d361d2e 100644
--- a/openbis_ng_ui/src/store/store.js
+++ b/openbis_ng_ui/src/store/store.js
@@ -5,11 +5,15 @@ import rootReducer from './reducers/reducers.js'
 import rootSaga from './sagas/sagas.js'
 import history from './history.js'
 
-function createStoreWithMiddleware(){
+function createStoreWithMiddleware() {
   const sagaMiddleware = createSagaMiddleware()
-  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
+  const composeEnhancers =
+    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
 
-  let store = createStore(rootReducer, composeEnhancers(applyMiddleware(...middlewares, sagaMiddleware)))
+  let store = createStore(
+    rootReducer,
+    composeEnhancers(applyMiddleware(...middlewares, sagaMiddleware))
+  )
   sagaMiddleware.run(rootSaga)
 
   history.configure(store)
diff --git a/openbis_ng_ui/srcTest/common/fixture.js b/openbis_ng_ui/srcTest/common/fixture.js
index 31054125e47f115f8e56d9f33dcbaa4d97837dd5..1f7035e15c2f4ccb751f88149151936259e6936a 100644
--- a/openbis_ng_ui/srcTest/common/fixture.js
+++ b/openbis_ng_ui/srcTest/common/fixture.js
@@ -16,19 +16,19 @@ export const ANOTHER_USER_DTO = {
 
 export const ALL_USERS_GROUP_DTO = {
   code: 'all-users-group',
-  users: [ TEST_USER_DTO, ANOTHER_USER_DTO ]
+  users: [TEST_USER_DTO, ANOTHER_USER_DTO]
 }
 
 export const TEST_GROUP_DTO = {
   code: 'test-group',
-  users: [ TEST_USER_DTO ]
+  users: [TEST_USER_DTO]
 }
 
 export const ANOTHER_GROUP_DTO = {
   code: 'another-group',
-  users: [ ANOTHER_USER_DTO ]
+  users: [ANOTHER_USER_DTO]
 }
 
-export function object(type, id){
+export function object(type, id) {
   return { type, id }
 }
diff --git a/openbis_ng_ui/srcTest/components/common/browser/browser.test.js b/openbis_ng_ui/srcTest/components/common/browser/browser.test.js
index 49aa1fef982ebc7ad35daca9d3b5745efe0b6c21..505a6f71be441547f5710d7b556f5f93979a5a39 100644
--- a/openbis_ng_ui/srcTest/components/common/browser/browser.test.js
+++ b/openbis_ng_ui/srcTest/components/common/browser/browser.test.js
@@ -17,30 +17,33 @@ beforeEach(() => {
 })
 
 describe('browser', () => {
-
   test('test', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
-      objects: [ fixture.TEST_GROUP_DTO, fixture.ANOTHER_GROUP_DTO, fixture.ALL_USERS_GROUP_DTO ]
+      objects: [
+        fixture.TEST_GROUP_DTO,
+        fixture.ANOTHER_GROUP_DTO,
+        fixture.ALL_USERS_GROUP_DTO
+      ]
     })
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
     store.dispatch(actions.init())
 
-    let wrapper = mount(<Browser store={store} page={pages.USERS}/>)
+    let wrapper = mount(<Browser store={store} page={pages.USERS} />)
 
     expectFilter(wrapper, '')
     expectNodes(wrapper, [
-      { level: 0, text: 'Users'},
-      { level: 0, text: 'Groups'}
+      { level: 0, text: 'Users' },
+      { level: 0, text: 'Groups' }
     ])
 
     simulateNodeIconClick(wrapper, 'users')
@@ -48,10 +51,10 @@ describe('browser', () => {
 
     expectFilter(wrapper, '')
     expectNodes(wrapper, [
-      { level: 0, text: 'Users'},
-      { level: 1, text: fixture.ANOTHER_USER_DTO.userId},
-      { level: 1, text: fixture.TEST_USER_DTO.userId},
-      { level: 0, text: 'Groups'}
+      { level: 0, text: 'Users' },
+      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
+      { level: 1, text: fixture.TEST_USER_DTO.userId },
+      { level: 0, text: 'Groups' }
     ])
 
     simulateFilterChange(wrapper, fixture.ANOTHER_GROUP_DTO.code.toUpperCase())
@@ -59,36 +62,39 @@ describe('browser', () => {
 
     expectFilter(wrapper, fixture.ANOTHER_GROUP_DTO.code.toUpperCase())
     expectNodes(wrapper, [
-      { level: 0, text: 'Users'},
-      { level: 1, text: fixture.ANOTHER_USER_DTO.userId},
-      { level: 2, text: fixture.ANOTHER_GROUP_DTO.code},
-      { level: 0, text: 'Groups'},
-      { level: 1, text: fixture.ANOTHER_GROUP_DTO.code}
+      { level: 0, text: 'Users' },
+      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
+      { level: 2, text: fixture.ANOTHER_GROUP_DTO.code },
+      { level: 0, text: 'Groups' },
+      { level: 1, text: fixture.ANOTHER_GROUP_DTO.code }
     ])
   })
-
 })
 
-function simulateNodeIconClick(wrapper, id){
-  wrapper.findWhere(node => {
-    return node.name() === 'BrowserNode' && node.prop('node').id === id
-  }).find('svg').first().simulate('click')
+function simulateNodeIconClick(wrapper, id) {
+  wrapper
+    .findWhere(node => {
+      return node.name() === 'BrowserNode' && node.prop('node').id === id
+    })
+    .find('svg')
+    .first()
+    .simulate('click')
 }
 
-function simulateFilterChange(wrapper, filter){
+function simulateFilterChange(wrapper, filter) {
   let input = wrapper.find('FilterField').find('input')
   input.instance().value = filter
   input.simulate('change')
 }
 
-function expectFilter(wrapper, expectedFilter){
+function expectFilter(wrapper, expectedFilter) {
   const actualFilter = wrapper.find('FilterField').map(node => {
     return node.prop('filter')
   })[0]
   expect(actualFilter).toEqual(expectedFilter)
 }
 
-function expectNodes(wrapper, expectedNodes){
+function expectNodes(wrapper, expectedNodes) {
   const actualNodes = wrapper.find('BrowserNode').map(node => {
     const text = node.prop('node').text
     const selected = node.prop('node').selected
diff --git a/openbis_ng_ui/srcTest/components/types/objectType/objectType.test.js b/openbis_ng_ui/srcTest/components/types/objectType/objectType.test.js
index ed87ef51021265d3d00b1a7bdbcedeb75bac607a..3067e88d92cf32c9e7ba560960038b8e90c2480f 100644
--- a/openbis_ng_ui/srcTest/components/types/objectType/objectType.test.js
+++ b/openbis_ng_ui/srcTest/components/types/objectType/objectType.test.js
@@ -16,9 +16,7 @@ beforeEach(() => {
 })
 
 describe('browser', () => {
-
-  test('test', (done) => {
-
+  test('test', done => {
     dto.SampleTypeFetchOptions.mockImplementation(() => ({
       withPropertyAssignments: () => ({
         withPropertyType: () => ({
@@ -46,79 +44,103 @@ describe('browser', () => {
       })
     }))
 
-    facade.getSampleTypes.mockReturnValue(Promise.resolve({
-      'TEST_OBJECT_TYPE': {
-        code: 'TEST_OBJECT_TYPE',
-        propertyAssignments: [{
-          propertyType: {
-            code: 'VARCHAR_PROPERTY_TYPE',
-            label: 'varchar label',
-            description: 'varchar description',
-            dataType: 'VARCHAR'
-          },
-          mandatory: false
-        },{
-          propertyType: {
-            code: 'MATERIAL_PROPERTY_TYPE',
-            label: 'material label',
-            description: 'material description',
-            dataType: 'MATERIAL'
-          },
-          mandatory: true
-        },{
-          propertyType: {
-            code: 'DICTIONARY_PROPERTY_TYPE',
-            label: 'dictionary label',
-            description: 'dictionary description',
-            dataType: 'CONTROLLEDVOCABULARY',
-            vocabulary: {
-              code: 'VOCABULARY_1'
+    facade.getSampleTypes.mockReturnValue(
+      Promise.resolve({
+        TEST_OBJECT_TYPE: {
+          code: 'TEST_OBJECT_TYPE',
+          propertyAssignments: [
+            {
+              propertyType: {
+                code: 'VARCHAR_PROPERTY_TYPE',
+                label: 'varchar label',
+                description: 'varchar description',
+                dataType: 'VARCHAR'
+              },
+              mandatory: false
+            },
+            {
+              propertyType: {
+                code: 'MATERIAL_PROPERTY_TYPE',
+                label: 'material label',
+                description: 'material description',
+                dataType: 'MATERIAL'
+              },
+              mandatory: true
+            },
+            {
+              propertyType: {
+                code: 'DICTIONARY_PROPERTY_TYPE',
+                label: 'dictionary label',
+                description: 'dictionary description',
+                dataType: 'CONTROLLEDVOCABULARY',
+                vocabulary: {
+                  code: 'VOCABULARY_1'
+                }
+              },
+              mandatory: true
             }
-          },
-          mandatory: true
-        }]
-      }
-    }))
+          ]
+        }
+      })
+    )
 
-    facade.searchPropertyTypes.mockReturnValue(Promise.resolve({
-      objects: [{
-        code: 'VARCHAR_PROPERTY_TYPE'
-      },{
-        code: 'MATERIAL_PROPERTY_TYPE'
-      },{
-        code: 'DICTIONARY_PROPERTY_TYPE'
-      }]
-    }))
+    facade.searchPropertyTypes.mockReturnValue(
+      Promise.resolve({
+        objects: [
+          {
+            code: 'VARCHAR_PROPERTY_TYPE'
+          },
+          {
+            code: 'MATERIAL_PROPERTY_TYPE'
+          },
+          {
+            code: 'DICTIONARY_PROPERTY_TYPE'
+          }
+        ]
+      })
+    )
 
-    facade.searchMaterials.mockReturnValue(Promise.resolve({
-      objects: [{
-        code: 'MATERIAL_1'
-      },{
-        code: 'MATERIAL_2'
-      }, {
-        code: 'MATERIAL_3'
-      }]
-    }))
+    facade.searchMaterials.mockReturnValue(
+      Promise.resolve({
+        objects: [
+          {
+            code: 'MATERIAL_1'
+          },
+          {
+            code: 'MATERIAL_2'
+          },
+          {
+            code: 'MATERIAL_3'
+          }
+        ]
+      })
+    )
 
-    facade.searchVocabularyTerms.mockReturnValue(Promise.resolve({
-      objects: [{
-        code: 'TERM_1'
-      },{
-        code: 'TERM_2'
-      }, {
-        code: 'TERM_3'
-      }]
-    }))
+    facade.searchVocabularyTerms.mockReturnValue(
+      Promise.resolve({
+        objects: [
+          {
+            code: 'TERM_1'
+          },
+          {
+            code: 'TERM_2'
+          },
+          {
+            code: 'TERM_3'
+          }
+        ]
+      })
+    )
 
     store.dispatch(actions.init())
 
     let wrapper = mount(
       <DragAndDropProvider>
-        <ObjectType store={store} objectId="TEST_OBJECT_TYPE"/>
+        <ObjectType store={store} objectId='TEST_OBJECT_TYPE' />
       </DragAndDropProvider>
     )
 
-    setTimeout(()=>{
+    setTimeout(() => {
       wrapper.update()
 
       expectTitle(wrapper, 'TEST_OBJECT_TYPE')
@@ -128,47 +150,62 @@ describe('browser', () => {
 
       expectPropertyMandatory(wrapper, 1, 'true')
       expectPropertyType(wrapper, 1, 'MATERIAL_PROPERTY_TYPE')
-      expectPropertyPreviewMaterial(wrapper, 1, ['', 'MATERIAL_1', 'MATERIAL_2', 'MATERIAL_3'])
+      expectPropertyPreviewMaterial(wrapper, 1, [
+        '',
+        'MATERIAL_1',
+        'MATERIAL_2',
+        'MATERIAL_3'
+      ])
 
       expectPropertyMandatory(wrapper, 2, 'true')
       expectPropertyType(wrapper, 2, 'DICTIONARY_PROPERTY_TYPE')
-      expectPropertyPreviewDictionary(wrapper, 2, ['', 'TERM_1', 'TERM_2', 'TERM_3'])
+      expectPropertyPreviewDictionary(wrapper, 2, [
+        '',
+        'TERM_1',
+        'TERM_2',
+        'TERM_3'
+      ])
 
       done()
     }, 0)
   })
-
 })
 
-function expectTitle(wrapper, expectedTitle){
+function expectTitle(wrapper, expectedTitle) {
   const actualTitle = wrapper.find('ObjectTypeTitle').text()
   expect(actualTitle).toEqual(expectedTitle)
 }
 
-function expectPropertyMandatory(wrapper, index, expected){
+function expectPropertyMandatory(wrapper, index, expected) {
   let row = wrapper.find('ObjectTypePropertyRow').at(index)
   let actual = row.find('ObjectTypePropertyMandatory').text()
   expect(actual).toEqual(expected)
 }
 
-function expectPropertyType(wrapper, index, expected){
+function expectPropertyType(wrapper, index, expected) {
   let row = wrapper.find('ObjectTypePropertyRow').at(index)
   let actual = row.find('ObjectTypePropertyType').text()
   expect(actual).toEqual(expected)
 }
 
-function expectPropertyPreviewMaterial(wrapper, index, expected){
+function expectPropertyPreviewMaterial(wrapper, index, expected) {
   let row = wrapper.find('ObjectTypePropertyRow').at(index)
-  let actual = row.find('ObjectTypePropertyPreview').find('option').map(node => {
-    return node.prop('value')
-  })
+  let actual = row
+    .find('ObjectTypePropertyPreview')
+    .find('option')
+    .map(node => {
+      return node.prop('value')
+    })
   expect(actual).toEqual(expected)
 }
 
-function expectPropertyPreviewDictionary(wrapper, index, expected){
+function expectPropertyPreviewDictionary(wrapper, index, expected) {
   let row = wrapper.find('ObjectTypePropertyRow').at(index)
-  let actual = row.find('ObjectTypePropertyPreview').find('option').map(node => {
-    return node.prop('value')
-  })
+  let actual = row
+    .find('ObjectTypePropertyPreview')
+    .find('option')
+    .map(node => {
+      return node.prop('value')
+    })
   expect(actual).toEqual(expected)
 }
diff --git a/openbis_ng_ui/srcTest/store/sagas/app.test.js b/openbis_ng_ui/srcTest/store/sagas/app.test.js
index a1bd8132322adcb742c3c00048df050c00ef1dcf..65f601caa7c8e58076160d2ce73e75a1efd7422b 100644
--- a/openbis_ng_ui/srcTest/store/sagas/app.test.js
+++ b/openbis_ng_ui/srcTest/store/sagas/app.test.js
@@ -35,7 +35,9 @@ describe('app', () => {
 
     const state = store.getState()
     expect(selectors.getSession(state)).toBeNull()
-    expect(selectors.getError(state)).toEqual({ message: 'Incorrect used or password' })
+    expect(selectors.getError(state)).toEqual({
+      message: 'Incorrect used or password'
+    })
   })
 
   test('logout', () => {
diff --git a/openbis_ng_ui/srcTest/store/sagas/browser.test.js b/openbis_ng_ui/srcTest/store/sagas/browser.test.js
index 50bdffa3e85442225f790c6511ead1a7b4a7912d..ccb87d516d9708a8493bab4a1ecf22fb42d1eb78 100644
--- a/openbis_ng_ui/srcTest/store/sagas/browser.test.js
+++ b/openbis_ng_ui/srcTest/store/sagas/browser.test.js
@@ -22,19 +22,22 @@ beforeEach(() => {
 })
 
 describe('browser', () => {
-
   test('init', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
-      objects: [ fixture.TEST_GROUP_DTO, fixture.ANOTHER_GROUP_DTO, fixture.ALL_USERS_GROUP_DTO ]
+      objects: [
+        fixture.TEST_GROUP_DTO,
+        fixture.ANOTHER_GROUP_DTO,
+        fixture.ALL_USERS_GROUP_DTO
+      ]
     })
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
@@ -44,24 +47,56 @@ describe('browser', () => {
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
       node(['users'], false, false, [
         node(['users', fixture.ANOTHER_USER_DTO.userId], false, false, [
-          node(['users', fixture.ANOTHER_USER_DTO.userId, fixture.ALL_USERS_GROUP_DTO.code]),
-          node(['users', fixture.ANOTHER_USER_DTO.userId, fixture.ANOTHER_GROUP_DTO.code])
+          node([
+            'users',
+            fixture.ANOTHER_USER_DTO.userId,
+            fixture.ALL_USERS_GROUP_DTO.code
+          ]),
+          node([
+            'users',
+            fixture.ANOTHER_USER_DTO.userId,
+            fixture.ANOTHER_GROUP_DTO.code
+          ])
         ]),
         node(['users', fixture.TEST_USER_DTO.userId], false, false, [
-          node(['users', fixture.TEST_USER_DTO.userId, fixture.ALL_USERS_GROUP_DTO.code]),
-          node(['users', fixture.TEST_USER_DTO.userId, fixture.TEST_GROUP_DTO.code])
+          node([
+            'users',
+            fixture.TEST_USER_DTO.userId,
+            fixture.ALL_USERS_GROUP_DTO.code
+          ]),
+          node([
+            'users',
+            fixture.TEST_USER_DTO.userId,
+            fixture.TEST_GROUP_DTO.code
+          ])
         ])
       ]),
       node(['groups'], false, false, [
         node(['groups', fixture.ALL_USERS_GROUP_DTO.code], false, false, [
-          node(['groups', fixture.ALL_USERS_GROUP_DTO.code, fixture.ANOTHER_USER_DTO.userId]),
-          node(['groups', fixture.ALL_USERS_GROUP_DTO.code, fixture.TEST_USER_DTO.userId])
+          node([
+            'groups',
+            fixture.ALL_USERS_GROUP_DTO.code,
+            fixture.ANOTHER_USER_DTO.userId
+          ]),
+          node([
+            'groups',
+            fixture.ALL_USERS_GROUP_DTO.code,
+            fixture.TEST_USER_DTO.userId
+          ])
         ]),
         node(['groups', fixture.ANOTHER_GROUP_DTO.code], false, false, [
-          node(['groups', fixture.ANOTHER_GROUP_DTO.code, fixture.ANOTHER_USER_DTO.userId])
+          node([
+            'groups',
+            fixture.ANOTHER_GROUP_DTO.code,
+            fixture.ANOTHER_USER_DTO.userId
+          ])
         ]),
         node(['groups', fixture.TEST_GROUP_DTO.code], false, false, [
-          node(['groups', fixture.TEST_GROUP_DTO.code, fixture.TEST_USER_DTO.userId])
+          node([
+            'groups',
+            fixture.TEST_GROUP_DTO.code,
+            fixture.TEST_USER_DTO.userId
+          ])
         ])
       ])
     ])
@@ -72,27 +107,40 @@ describe('browser', () => {
 
   test('filter', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
-      objects: [ fixture.TEST_GROUP_DTO, fixture.ANOTHER_GROUP_DTO, fixture.ALL_USERS_GROUP_DTO ]
+      objects: [
+        fixture.TEST_GROUP_DTO,
+        fixture.ANOTHER_GROUP_DTO,
+        fixture.ALL_USERS_GROUP_DTO
+      ]
     })
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
     store.dispatch(actions.browserInit(pages.USERS))
-    store.dispatch(actions.browserFilterChange(pages.USERS, fixture.ANOTHER_GROUP_DTO.code.toUpperCase()))
+    store.dispatch(
+      actions.browserFilterChange(
+        pages.USERS,
+        fixture.ANOTHER_GROUP_DTO.code.toUpperCase()
+      )
+    )
 
     const state = store.getState()
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
       node(['users'], true, false, [
         node(['users', fixture.ANOTHER_USER_DTO.userId], true, false, [
-          node(['users', fixture.ANOTHER_USER_DTO.userId, fixture.ANOTHER_GROUP_DTO.code])
+          node([
+            'users',
+            fixture.ANOTHER_USER_DTO.userId,
+            fixture.ANOTHER_GROUP_DTO.code
+          ])
         ])
       ]),
       node(['groups'], true, false, [
@@ -106,7 +154,7 @@ describe('browser', () => {
 
   test('select node', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
@@ -115,14 +163,22 @@ describe('browser', () => {
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
-    let testUserObject = fixture.object(objectType.USER, fixture.TEST_USER_DTO.userId)
+    let testUserObject = fixture.object(
+      objectType.USER,
+      fixture.TEST_USER_DTO.userId
+    )
 
     store.dispatch(actions.browserInit(pages.USERS))
-    store.dispatch(actions.browserNodeSelect(pages.USERS, nodeId(['users', fixture.TEST_USER_DTO.userId])))
+    store.dispatch(
+      actions.browserNodeSelect(
+        pages.USERS,
+        nodeId(['users', fixture.TEST_USER_DTO.userId])
+      )
+    )
 
     let state = store.getState()
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
@@ -139,7 +195,7 @@ describe('browser', () => {
 
   test('select another node', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
@@ -148,16 +204,32 @@ describe('browser', () => {
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
-    let testUserObject = fixture.object(objectType.USER, fixture.TEST_USER_DTO.userId)
-    let anotherUserObject = fixture.object(objectType.USER, fixture.ANOTHER_USER_DTO.userId)
+    let testUserObject = fixture.object(
+      objectType.USER,
+      fixture.TEST_USER_DTO.userId
+    )
+    let anotherUserObject = fixture.object(
+      objectType.USER,
+      fixture.ANOTHER_USER_DTO.userId
+    )
 
     store.dispatch(actions.browserInit(pages.USERS))
-    store.dispatch(actions.browserNodeSelect(pages.USERS, nodeId(['users', fixture.TEST_USER_DTO.userId])))
-    store.dispatch(actions.browserNodeSelect(pages.USERS, nodeId(['users', fixture.ANOTHER_USER_DTO.userId])))
+    store.dispatch(
+      actions.browserNodeSelect(
+        pages.USERS,
+        nodeId(['users', fixture.TEST_USER_DTO.userId])
+      )
+    )
+    store.dispatch(
+      actions.browserNodeSelect(
+        pages.USERS,
+        nodeId(['users', fixture.ANOTHER_USER_DTO.userId])
+      )
+    )
 
     let state = store.getState()
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
@@ -174,7 +246,7 @@ describe('browser', () => {
 
   test('select virtual node', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
@@ -183,7 +255,7 @@ describe('browser', () => {
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
@@ -205,34 +277,58 @@ describe('browser', () => {
 
   test('select two nodes that represent the same object', () => {
     facade.searchPersons.mockReturnValue({
-      objects: [ fixture.TEST_USER_DTO ]
+      objects: [fixture.TEST_USER_DTO]
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
-      objects: [ fixture.TEST_GROUP_DTO ]
+      objects: [fixture.TEST_GROUP_DTO]
     })
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
-    let testUserObject = fixture.object(objectType.USER, fixture.TEST_USER_DTO.userId)
+    let testUserObject = fixture.object(
+      objectType.USER,
+      fixture.TEST_USER_DTO.userId
+    )
 
     store.dispatch(actions.browserInit(pages.USERS))
-    store.dispatch(actions.browserNodeSelect(pages.USERS, nodeId(['users', fixture.TEST_USER_DTO.userId])))
+    store.dispatch(
+      actions.browserNodeSelect(
+        pages.USERS,
+        nodeId(['users', fixture.TEST_USER_DTO.userId])
+      )
+    )
 
     let state = store.getState()
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
       node(['users'], false, false, [
         node(['users', fixture.TEST_USER_DTO.userId], false, true, [
-          node(['users', fixture.TEST_USER_DTO.userId, fixture.TEST_GROUP_DTO.code], false, false)
+          node(
+            [
+              'users',
+              fixture.TEST_USER_DTO.userId,
+              fixture.TEST_GROUP_DTO.code
+            ],
+            false,
+            false
+          )
         ])
       ]),
       node(['groups'], false, false, [
         node(['groups', fixture.TEST_GROUP_DTO.code], false, false, [
-          node(['groups', fixture.TEST_GROUP_DTO.code, fixture.TEST_USER_DTO.userId], false, true)
+          node(
+            [
+              'groups',
+              fixture.TEST_GROUP_DTO.code,
+              fixture.TEST_USER_DTO.userId
+            ],
+            false,
+            true
+          )
         ])
       ])
     ])
@@ -240,18 +336,43 @@ describe('browser', () => {
     expectSelectedObject(pages.USERS, testUserObject)
     expectOpenObjects(pages.USERS, [testUserObject])
 
-    store.dispatch(actions.browserNodeSelect(pages.USERS, nodeId(['groups', fixture.TEST_GROUP_DTO.code, fixture.TEST_USER_DTO.userId])))
+    store.dispatch(
+      actions.browserNodeSelect(
+        pages.USERS,
+        nodeId([
+          'groups',
+          fixture.TEST_GROUP_DTO.code,
+          fixture.TEST_USER_DTO.userId
+        ])
+      )
+    )
 
     state = store.getState()
     expectNodes(selectors.createGetBrowserNodes()(state, pages.USERS), [
       node(['users'], false, false, [
         node(['users', fixture.TEST_USER_DTO.userId], false, true, [
-          node(['users', fixture.TEST_USER_DTO.userId, fixture.TEST_GROUP_DTO.code], false, false)
+          node(
+            [
+              'users',
+              fixture.TEST_USER_DTO.userId,
+              fixture.TEST_GROUP_DTO.code
+            ],
+            false,
+            false
+          )
         ])
       ]),
       node(['groups'], false, false, [
         node(['groups', fixture.TEST_GROUP_DTO.code], false, false, [
-          node(['groups', fixture.TEST_GROUP_DTO.code, fixture.TEST_USER_DTO.userId], false, true)
+          node(
+            [
+              'groups',
+              fixture.TEST_GROUP_DTO.code,
+              fixture.TEST_USER_DTO.userId
+            ],
+            false,
+            true
+          )
         ])
       ])
     ])
@@ -266,12 +387,12 @@ describe('browser', () => {
     })
 
     facade.searchAuthorizationGroups.mockReturnValue({
-      objects: [ fixture.TEST_GROUP_DTO ]
+      objects: [fixture.TEST_GROUP_DTO]
     })
 
     dto.AuthorizationGroupFetchOptions.mockImplementation(() => {
       return {
-        withUsers: function(){}
+        withUsers: function() {}
       }
     })
 
@@ -302,34 +423,33 @@ describe('browser', () => {
     expectSelectedObject(pages.USERS, null)
     expectOpenObjects(pages.USERS, [])
   })
-
 })
 
-function nodeId(idParts){
+function nodeId(idParts) {
   return idParts.join('/')
 }
 
-function node(idParts, expanded = false, selected = false, children){
+function node(idParts, expanded = false, selected = false, children) {
   let node = {
     id: nodeId(idParts),
     expanded,
     selected
   }
 
-  if(children !== undefined){
+  if (children !== undefined) {
     node['children'] = children
   }
 
   return node
 }
 
-function expectNodes(actualNodes, expectedNodes){
+function expectNodes(actualNodes, expectedNodes) {
   const keys = new Set(['id', 'expanded', 'selected', 'children'])
 
   var actualNodesClone = _.cloneDeep(actualNodes)
   common.mapNodes(null, actualNodesClone, (parent, node) => {
     _.keys(node).forEach(key => {
-      if(!keys.has(key)){
+      if (!keys.has(key)) {
         delete node[key]
       }
     })
@@ -339,10 +459,12 @@ function expectNodes(actualNodes, expectedNodes){
   expect(actualNodesClone).toEqual(expectedNodes)
 }
 
-function expectSelectedObject(page, object){
-  expect(selectors.createGetSelectedObject()(store.getState(), page)).toEqual(object)
+function expectSelectedObject(page, object) {
+  expect(selectors.createGetSelectedObject()(store.getState(), page)).toEqual(
+    object
+  )
 }
 
-function expectOpenObjects(page, objects){
+function expectOpenObjects(page, objects) {
   expect(selectors.getOpenObjects(store.getState(), page)).toEqual(objects)
 }
diff --git a/openbis_ng_ui/srcTest/store/sagas/page.test.js b/openbis_ng_ui/srcTest/store/sagas/page.test.js
index 7c779c1225f1a3b057d603e75bb90922b72c42e1..4671f9e11f066270d8a199519a3fc63254c0364c 100644
--- a/openbis_ng_ui/srcTest/store/sagas/page.test.js
+++ b/openbis_ng_ui/srcTest/store/sagas/page.test.js
@@ -15,10 +15,12 @@ beforeEach(() => {
 })
 
 describe('page', () => {
-
   test('objectOpen objectClose', () => {
     let object1 = fixture.object(objectType.USER, fixture.TEST_USER_DTO.userId)
-    let object2 = fixture.object(objectType.USER, fixture.ANOTHER_USER_DTO.userId)
+    let object2 = fixture.object(
+      objectType.USER,
+      fixture.ANOTHER_USER_DTO.userId
+    )
     let object3 = fixture.object(objectType.GROUP, fixture.TEST_GROUP_DTO.code)
 
     store.dispatch(actions.objectOpen(pages.USERS, object1.type, object1.id))
@@ -51,14 +53,13 @@ describe('page', () => {
     expectSelectedObject(pages.USERS, null)
     expectOpenObjects(pages.USERS, [])
   })
-
 })
 
-function expectSelectedObject(page, object){
+function expectSelectedObject(page, object) {
   let getSelectedObject = selectors.createGetSelectedObject()
   expect(getSelectedObject(store.getState(), page)).toEqual(object)
 }
 
-function expectOpenObjects(page, objects){
+function expectOpenObjects(page, objects) {
   expect(selectors.getOpenObjects(store.getState(), page)).toEqual(objects)
 }
diff --git a/openbis_ng_ui/webpack.config.dev.js b/openbis_ng_ui/webpack.config.dev.js
index c429af25ffc681e744f176a39cc3b158739ead8a..5afd22033e6228d69cb8734723f092b80587f923 100644
--- a/openbis_ng_ui/webpack.config.dev.js
+++ b/openbis_ng_ui/webpack.config.dev.js
@@ -10,26 +10,26 @@ module.exports = {
   },
 
   devServer: {
-    host: "0.0.0.0",
+    host: '0.0.0.0',
     port: 8124,
     inline: true,
-    contentBase: "./src",
+    contentBase: './src',
     https: false,
     watchOptions: {
       aggregateTimeout: 300,
       poll: 1000
     },
     proxy: {
-      "/openbis": {
-        "target": 'http://localhost:8888',
-        "pathRewrite": {'^/openbis/resources' : '/openbis-test/resources'},
-        "changeOrigin": true,
-        "secure": false,
+      '/openbis': {
+        target: 'http://localhost:8888',
+        pathRewrite: { '^/openbis/resources': '/openbis-test/resources' },
+        changeOrigin: true,
+        secure: false
       }
     }
   },
 
-  devtool: "source-map",
+  devtool: 'source-map',
 
   mode: 'development',
 
@@ -39,15 +39,12 @@ module.exports = {
         test: /\.(js|jsx)$/,
         exclude: /node_modules/,
         use: {
-          loader: "babel-loader"
+          loader: 'babel-loader'
         }
       },
       {
         test: /\.(css)$/,
-        use: [
-          'style-loader',
-          'css-loader'
-        ]
+        use: ['style-loader', 'css-loader']
       },
       {
         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
@@ -65,7 +62,10 @@ module.exports = {
       filename: './index.html',
       template: './index.html'
     }),
-//    new Webpack.WatchIgnorePlugin(['/home/vagrant/openbis/openbis_ng_ui/react/node_modules/', '/home/vagrant/openbis/openbis_ng_ui/react/node/'])
-    new Webpack.WatchIgnorePlugin([new RegExp("/node_modules/"), new RegExp("/node/")])
+    //    new Webpack.WatchIgnorePlugin(['/home/vagrant/openbis/openbis_ng_ui/react/node_modules/', '/home/vagrant/openbis/openbis_ng_ui/react/node/'])
+    new Webpack.WatchIgnorePlugin([
+      new RegExp('/node_modules/'),
+      new RegExp('/node/')
+    ])
   ]
 }
diff --git a/openbis_ng_ui/webpack.config.js b/openbis_ng_ui/webpack.config.js
index d6d7ba256aba882a80fab8a120d34cdd5818ac14..17d891f631f5fb485b2753770eef02b907f44a24 100644
--- a/openbis_ng_ui/webpack.config.js
+++ b/openbis_ng_ui/webpack.config.js
@@ -1,5 +1,5 @@
 /* eslint-disable */
-const HtmlWebpackPlugin = require('html-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin')
 
 module.exports = {
   entry: './src/index.js',
@@ -16,15 +16,12 @@ module.exports = {
         test: /\.(js|jsx)$/,
         exclude: /node_modules/,
         use: {
-          loader: "babel-loader"
+          loader: 'babel-loader'
         }
       },
       {
         test: /\.(css)$/,
-        use: [
-          'style-loader',
-          'css-loader'
-        ]
+        use: ['style-loader', 'css-loader']
       },
       {
         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
@@ -43,4 +40,4 @@ module.exports = {
       template: './index.html'
     })
   ]
-};
+}
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls
index 8b219a0b5747b886494c221c82bd3f3f1f470262..dc41839238990f7688c5a4b4f956986f3414f318 100644
Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls and b/openbis_standard_technologies/dist/core-plugins/eln-lims-life-sciences/1/as/master-data/data-model.xls differ
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls
index 16332eb71f3a9ddbf34d818a04319e5fb81200c1..21bc5da6b913898b698b337c5eaa1dd604c75b92 100644
Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls differ
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
index b713b303923d04fa508d679c6db1851f4d2ce138..6027757010cb5d97163a8d04c21d03cdcbc93eae 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/Profile.js
@@ -61,7 +61,8 @@ $.extend(DefaultProfile.prototype, {
 				showVocabularyViewer : true,
 				showUserManager : true,
 				showUserProfile : true,
-				showResearchCollectionExportBuilder : false,
+			    // Not fully implemented yet.
+				// showResearchCollectionExportBuilder : false,
 				showZenodoExportBuilder : false,
 		}
 		
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
index a4c8633e158bed6a2a2c053e454cc75e19aeff45..4a42b1c0c13a61c3ff253c9f5535a822f8707862 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
@@ -83,13 +83,35 @@ function MainController(profile) {
 	
 	//Functionality to keep state
 	this.backStack = [];
+
+	this.zenodoApiTokenKey = "personal-zenodo-api-token";
 	
 	//
 	// Validates and enters the app
 	//
-	
+
 	this.enterApp = function(data, username, password) {
+	    var _this = this;
+	    if(data && !username && !password) {
+	        this.openbisV1.listDataStores(function(result) {
+	            if(result && result.error && result.error.message) {
+	                var callback = function() {Util.unblockUI();};
+	                Util.showUserError(result.error.message, callback);
+	            } else {
+	                _this.initApp(data, username, password);
+	            }
+	        });
+	    } else {
+	        this.initApp(data, username, password);
+	    }
+	}
+
+	this.initApp = function(data, username, password) {
 		var localReference = this;
+		//
+		// Check
+		//
+
 		//
 		// Check Credentials
 		//
@@ -197,6 +219,9 @@ function MainController(profile) {
 													
 													Util.unblockUI();
 													LayoutManager.resize(mainController.views, true); // Maybe fixes white screen on startup?
+
+													// Keep Alive
+													localReference.serverFacade.scheduleKeepAlive();
 												});
 											};
 											
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js
index 4cfa7f5bc6f39d2437a318ff913f9341cd1c48f1..6c5dfe97af5360ab100cd3f55b4ac2af1f306459 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js
@@ -80,15 +80,19 @@ $.extend(MicroscopyTechnology.prototype, ELNLIMSPlugin.prototype, {
 
                             // Show the series name instead of the dataset code
                             view.getDataSetText = function (dataSetCode) {
+                                var displayName = dataSetCode;
+
                                 // Return the series name
                                 for (var i = 0; i < model.datasets.length; i++) {
-                                    if (model.datasets[i].code === dataSetCode) {
-                                        return model.datasets[0].properties["$NAME"];
+                                    if (model.datasets[i].code === dataSetCode &&
+                                        model.datasets[i].properties[profile.propertyReplacingCode]) {
+                                        displayName = model.datasets[i].properties[profile.propertyReplacingCode];
+                                        break;
                                     }
                                 }
 
                                 // If not found, return the dataset code
-                                return dataSetCode;
+                                return displayName;
                             };
 
                         });
@@ -125,13 +129,13 @@ $.extend(MicroscopyTechnology.prototype, ELNLIMSPlugin.prototype, {
 
                             // Show the series name instead of the dataset code
                             view.getDataSetText = function (dataSetCode) {
+                                var displayName = dataSetCode;
                                 // Return the series name
-                                if (model.dataSet.code === dataSetCode) {
-                                    return model.dataSet.properties["$NAME"];
-                                } else {
-                                    // Fall-back (that should not happen)
-                                    return dataSetCode;
+                                if (model.dataSet.code === dataSetCode &&
+                                    model.dataSet.properties[profile.propertyReplacingCode]) {
+                                    displayName = model.dataSet.properties[profile.propertyReplacingCode];
                                 }
+                                return displayName;
                             };
 
                         });
@@ -189,11 +193,9 @@ $.extend(MicroscopyTechnology.prototype, ELNLIMSPlugin.prototype, {
             }
 
             // Prepare the name to be shown
-            var name;
-            if (sample.properties["$NAME"]) {
-                name = sample.properties["$NAME"];
-            } else {
-                name = sample.code;
+            var name = sample.code;
+            if (sample.properties[profile.propertyReplacingCode]) {
+                name = sample.properties[profile.propertyReplacingCode];
             }
 
             // Make sure it is not too long
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
index 96e277604e4a4ba2387881404142543ed6e52463..cf273afe3bfe7883c1fbe5f6e7775c43a7243742 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
@@ -51,7 +51,8 @@ function ServerFacade(openbisServer) {
 	var responseInterceptor = function(response, action){
 		var isError = false;
 		if(response && response.error) {
-			if(response.error.message === "Session no longer available. Please login again.") {
+			if(response.error.message === "Session no longer available. Please login again."
+					|| response.error.message.endsWith("is invalid: user is not logged in.")) {
 				isError = true;
 				Util.showError(response.error.message, function() {
 					location.reload(true);
@@ -184,7 +185,27 @@ function ServerFacade(openbisServer) {
         });
 	}
 	*/
-	
+
+    this.scheduleKeepAlive = function() {
+        var _this = this;
+        var TIMEOUT = 60000; //60 Seconds
+
+        setTimeout(function(){
+            _this.keepAlive();
+        }, TIMEOUT);
+    }
+
+	this.keepAlive = function() {
+	    var _this = this;
+	    mainController.openbisV3.isSessionActive().done(function(isSessionActive) {
+	        var timeStamp = Math.floor(Date.now() / 1000);
+            _this.scheduleKeepAlive();
+	    }).fail(function(error) {
+            var timeStamp = Math.floor(Date.now() / 1000);
+            _this.scheduleKeepAlive();
+        });
+	}
+
 	this.getPersons = function(personIds, callbackFunction) {
 		if(!mainController.openbisV3.getPersons) {
 			return null; // In case the method doesn't exist, do nothing
@@ -396,7 +417,7 @@ function ServerFacade(openbisServer) {
 			});
 	};
 
-    this.exportZenodo = function(entities, includeRoot, metadataOnly, userInformation, callbackFunction) {
+    this.exportZenodo = function(entities, includeRoot, metadataOnly, userInformation, title, accessToken, callbackFunction) {
         this.asyncExportZenodo({
             "method": "exportAll",
             "includeRoot": includeRoot,
@@ -405,6 +426,8 @@ function ServerFacade(openbisServer) {
             "userInformation": userInformation,
             "originUrl": window.location.origin,
             "sessionToken": this.openbisServer.getSession(),
+			"submissionTitle": title,
+			"accessToken": accessToken
         }, callbackFunction, "zenodo-exports-api");
     };
 
@@ -420,7 +443,6 @@ function ServerFacade(openbisServer) {
 				var options = new AggregationServiceExecutionOptions();
 
 				options.withParameter("sessionToken", parameters["sessionToken"]);
-
 				options.withParameter("entities", parameters["entities"]);
 				options.withParameter("includeRoot", parameters["includeRoot"]);
 				options.withParameter("metadataOnly", parameters["metadataOnly"]);
@@ -429,6 +451,8 @@ function ServerFacade(openbisServer) {
 				options.withParameter("submissionType", parameters["submissionType"]);
 				options.withParameter("submissionUrl", parameters["submissionUrl"]);
 				options.withParameter("entities", parameters["entities"]);
+				options.withParameter("submissionTitle", parameters["submissionTitle"]);
+				options.withParameter("accessToken", parameters["accessToken"]);
 				options.withParameter("userId", parameters["userInformation"]["id"]);
 				options.withParameter("userEmail", parameters["userInformation"]["email"]);
 				options.withParameter("userFirstName", parameters["userInformation"]["firstName"]);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/JExcelEditorManager.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/JExcelEditorManager.js
index 4c1ab4c4bf814f16365bc0b97ff969d0d85277be..8243e23ce83bab15d142dc9e1940e36d27755669 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/JExcelEditorManager.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/JExcelEditorManager.js
@@ -7,15 +7,16 @@ var JExcelEditorManager = new function() {
         return function(el, record, x, y, value) {
             var jExcelEditor = _this.jExcelEditors[guid];
             if(jExcelEditor) {
-                // Change column width
-                var columnWidth = parseInt(jExcelEditor.getWidth(x));
-                var td = el.children[1].children[0].children[2].children[y].children[parseInt(x)+1];
-                var columnScrollWidth = td.scrollWidth;
-
-                if(columnScrollWidth > columnWidth) {
-                    jExcelEditor.setWidth(x, columnScrollWidth + 10);
+                if(value) {
+                    // Change column width
+                    var columnWidth = parseInt(jExcelEditor.getWidth(x));
+                    var td = el.children[1].children[0].children[2].children[y].children[parseInt(x)+1];
+                    var columnScrollWidth = td.scrollWidth;
+
+                    if(columnScrollWidth > columnWidth) {
+                        jExcelEditor.setWidth(x, columnScrollWidth + 10);
+                    }
                 }
-
                 // Save Editor
                 var data = jExcelEditor.getData();
                 var style = jExcelEditor.getStyle();
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ProjectForm/ProjectFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ProjectForm/ProjectFormView.js
index 67397978ff96378c0be5e27ba058d66d09281d25..4ce098a9fb4f4424a97ece930ee640d19c97b63e 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ProjectForm/ProjectFormView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ProjectForm/ProjectFormView.js
@@ -78,7 +78,7 @@ function ProjectFormView(projectFormController, projectFormModel) {
 							if (experimentType) {
 								defaultValue = _this._projectFormModel.project.spaceCode;
 							} else {
-								defaultValue = "";
+								defaultValue = "COLLECTION";
 							}
 						} else {
 							defaultValue = "DEFAULT_EXPERIMENT";
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
index ecd57517a2c01f717c852ea89b205c396594b486..8cbb61294a164f081712b1385f5ad793adc4eb3d 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
@@ -240,7 +240,8 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
          Â Ã‚ Ã‚ treeModelUtils.push({ title : jupyterNotebook, entityType: "NEW_JUPYTER_NOTEBOOK", key : "NEW_JUPYTER_NOTEBOOK", folder : false, lazy : false, view : "showNewJupyterNotebookCreator" });
         }
         
-        if(profile.mainMenu.showUserProfile && profile.isFileAuthenticationService && profile.isFileAuthenticationUser) {
+        if(profile.mainMenu.showUserProfile) {
+            // && profile.isFileAuthenticationService && profile.isFileAuthenticationUser
          Â Ã‚ Ã‚ var userProfileLink = _this.getLinkForNode("User Profile", "USER_PROFILE", "showUserProfilePage", null);
          Â Ã‚ Ã‚ treeModelUtils.push({ title : userProfileLink, entityType: "USER_PROFILE", key : "USER_PROFILE", folder : false, lazy : false, view : "showUserProfilePage", icon : "glyphicon glyphicon-user" });
         }
@@ -276,15 +277,16 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
                 });
             }
 
-            if (profile.mainMenu.showResearchCollectionExportBuilder) {
-                var researchCollectionExportBuilderLink = _this.getLinkForNode("Export to Research Collection",
-                        "EXPORT_TO_RESEARCH_COLLECTION", "showResearchCollectionExportPage", null);
-                treeModelExports.push({
-                    displayName: "Export to Research Collection", title: researchCollectionExportBuilderLink,
-                    entityType: "EXPORT_TO_RESEARCH_COLLECTION", key: "EXPORT_TO_RESEARCH_COLLECTION", folder: false, lazy: false,
-                    view: "showResearchCollectionExportPage", icon: "./img/research-collection-icon.png"
-                });
-            }
+            // Not fully implemented yet.
+            // if (profile.mainMenu.showResearchCollectionExportBuilder) {
+            //     var researchCollectionExportBuilderLink = _this.getLinkForNode("Export to Research Collection",
+            //             "EXPORT_TO_RESEARCH_COLLECTION", "showResearchCollectionExportPage", null);
+            //     treeModelExports.push({
+            //         displayName: "Export to Research Collection", title: researchCollectionExportBuilderLink,
+            //         entityType: "EXPORT_TO_RESEARCH_COLLECTION", key: "EXPORT_TO_RESEARCH_COLLECTION", folder: false, lazy: false,
+            //         view: "showResearchCollectionExportPage", icon: "./img/research-collection-icon.png"
+            //     });
+            // }
 
             if (profile.mainMenu.showZenodoExportBuilder) {
                 var zenodoExportBuilderLink = _this.getLinkForNode("Export to Zenodo", "EXPORT_TO_ZENODO", "showZenodoExportPage", null);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileController.js
index 74c870c9621ff3375bbb3817d4d63ead3cee369c..93d3c3fc9e2ebfd73e007cdd00da66bee76928ac 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileController.js
@@ -18,6 +18,7 @@ function UserProfileController(mainController, mode) {
 	this._mainController = mainController;
 	this._userProfileModel = new UserProfileModel(mode);
 	this._userProfileView = new UserProfileView(this, this._userProfileModel);
+    this._zenodoApiTokenKey = this._mainController.zenodoApiTokenKey;
 
 	this.init = function(views) {
         this._userProfileView.repaint(views);
@@ -49,6 +50,8 @@ function UserProfileController(mainController, mode) {
 			return;
 		}
 		var userId = this._mainController.serverFacade.getUserId();
+
+		this.setSettingValue(this._zenodoApiTokenKey, userInformation.zenodoToken);
 		this._mainController.serverFacade.updateUserInformation(userId, userInformation, (function(ok) {
 			if (ok) {
 				Util.showInfo("Profile saved. You will be logged out automatically in order to reload the profile data upon login.", (function() {
@@ -69,4 +72,17 @@ function UserProfileController(mainController, mode) {
 		return errors;
 	}
 
+	this.isFileAuthentication = function() {
+		return this._mainController.profile.isFileAuthenticationService &&
+				this._mainController.profile.isFileAuthenticationUser;
+	}
+
+	this.getSettingValue = function (key, callback) {
+		this._mainController.serverFacade.getSetting(key, callback);
+	};
+
+	this.setSettingValue = function (key, value) {
+		this._mainController.serverFacade.setSetting(key, value);
+	};
+
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileView.js
index 22e972b17ff4a1c0ed7cd41180acf7d7211b6d91..65d80e7574d388b5922b96ea0459bc8b35175128 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/UserProfile/UserProfileView.js
@@ -21,6 +21,7 @@ function UserProfileView(userProfileController, userProfileModel) {
     this._$firstNameInput = null;
     this._$lastNameInput = null;
     this._$emailInput = null;
+    this._$zenodoToken = null;
 
 	this.repaint = function(views) {
 
@@ -71,14 +72,28 @@ function UserProfileView(userProfileController, userProfileModel) {
             this._$emailInput = $("<input>", { type : "text", class : "form-control" });
             this._$emailInput.val(getUserInformation.email);
             $formColumn.append(this._getFormGroup(this._$emailInput, "Email:"));
+            // personal Zenodo API token
+            this._$zenodoToken = $("<input>", { type : "text", class : "form-control" });
+
+            this._userProfileController.getSettingValue(this._userProfileController._zenodoApiTokenKey, (function (settingsValue) {
+                if (settingsValue) {
+                    this._$zenodoToken.val(settingsValue.trim());
+                }
+            }).bind(this));
+            $formColumn.append(this._getFormGroup(this._$zenodoToken, "Zenodo API Token:"));
+
             // disable in view mode
-            if (this._userProfileModel.mode === FormMode.VIEW) {
+            if (this._userProfileModel.mode === FormMode.VIEW ||
+                    !this._userProfileController.isFileAuthentication()) {
                 this._$firstNameInput.prop("disabled", true);
                 this._$lastNameInput.prop("disabled", true);
                 this._$emailInput.prop("disabled", true);
             }
-        }).bind(this));
 
+            if (this._userProfileModel.mode === FormMode.VIEW) {
+                this._$zenodoToken.prop("disabled", true);
+            }
+        }).bind(this));
     }
 
 	this._getOptionsMenu = function() {
@@ -96,6 +111,7 @@ function UserProfileView(userProfileController, userProfileModel) {
             firstName : this._$firstNameInput.val(),
             lastName : this._$lastNameInput.val(),
             email : this._$emailInput.val(),
+            zenodoToken : this._$zenodoToken.val()
         };
     }
 
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js
index 34f489234de0f0119b948bb8139ea5a7e76855ff..fb99fc614e7ea5cb2e89a2a84a54680df5e6d190 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportController.js
@@ -15,16 +15,26 @@
  */
 
 function ZenodoExportController(parentController) {
-    var exportModel = new ZenodoExportModel();
-    var exportView = new ZenodoExportView(this, exportModel);
+    this.exportModel = null;
+    this.exportView = null;
+    this.zenodoApiTokenKey = parentController.zenodoApiTokenKey;
 
     this.init = function(views) {
-        exportView.repaint(views);
+        this.getSettingValue(this.zenodoApiTokenKey, (function (accessToken) {
+            if (accessToken && accessToken !== '') {
+                this.exportModel = new ZenodoExportModel(accessToken);
+                this.exportView = new ZenodoExportView(this, this.exportModel);
+                this.exportView.repaint(views);
+            } else {
+                Util.showError('Personal Zenodo API Token missing, please set it on your user profile.');
+            }
+        }).bind(this));
     };
 
     this.exportSelected = function() {
         var _this = this;
-        var selectedNodes = $(exportModel.tree).fancytree('getTree').getSelectedNodes();
+        var selectedNodes = $(this.exportModel.tree).fancytree('getTree').getSelectedNodes();
+        var title = this.exportView.$titleTextBox.val().trim();
 
         var toExport = [];
         for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) {
@@ -34,12 +44,14 @@ function ZenodoExportController(parentController) {
 
         if (toExport.length === 0) {
             Util.showInfo('First select something to export.');
+        } else if (title === "") {
+            Util.showInfo('Please enter a title.');
         } else if (!this.isValid(toExport)) {
             Util.showInfo('Not only spaces and the root should be selected. It will result in an empty export file.');
         } else {
             Util.blockUI();
-            this.getUserInformation(function(userInformation) {
-                mainController.serverFacade.exportZenodo(toExport, true, false, userInformation,
+            this.getUserInformation((function(userInformation) {
+                mainController.serverFacade.exportZenodo(toExport, true, false, userInformation, title, this.exportModel.accessToken,
                         function(operationExecutionPermId) {
                             _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) {
                                 Util.unblockUI();
@@ -56,7 +68,7 @@ function ZenodoExportController(parentController) {
                                 }
                             });
                         });
-            });
+            }).bind(this));
         }
     };
 
@@ -109,4 +121,8 @@ function ZenodoExportController(parentController) {
             callback(userInformation);
         });
     };
+
+    this.getSettingValue = function (key, callback) {
+        parentController.serverFacade.getSetting(key, callback);
+    };
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js
index f555844b57e82e8c640965767f2077b1b66f213e..dc4f73489ca42ef5f9dc9cd1abe15ef67a0a8c35 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportModel.js
@@ -14,5 +14,6 @@
  * limitations under the License.
  */
 
-function ZenodoExportModel() {
+function ZenodoExportModel(accessToken) {
+    this.accessToken = accessToken;
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js
index 775a8893be6bbaed639dd4409ab6e73e14e56b43..a2ae6996d23f4730a24de4e2ff05f4b6df87fae4 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ZenodoExport/ZenodoExportView.js
@@ -21,7 +21,7 @@ function ZenodoExportView(exportController, exportModel) {
 
         var $form = $("<div>");
         var $formColumn = $("<form>", {
-            'name': 'rcExportForm',
+            'name': 'zenodoExportForm',
             'role': 'form',
             'action': 'javascript:void(0);',
             'onsubmit': 'mainController.currentView.exportSelected();'
@@ -53,8 +53,17 @@ function ZenodoExportView(exportController, exportModel) {
         var $formTitle = $('<h2>').append('Zenodo Export Builder');
         $header.append($formTitle);
 
+        this.paintTitleTextBox($container);
+
         var $exportButton = $('<input>', { 'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected',
-            'onClick': '$("form[name=\'rcExportForm\']").submit()'});
+            'onClick': '$("form[name=\'zenodoExportForm\']").submit()'});
         $header.append($exportButton);
     };
+
+    this.paintTitleTextBox = function ($container) {
+        this.$titleTextBox = FormUtil.getTextInputField('zenodo-submission-title', 'Submission title', true);
+        var titleTextBoxFormGroup = FormUtil.getFieldForComponentWithLabel(this.$titleTextBox, 'Submission Title', null, true);
+        titleTextBoxFormGroup.css('width', '50%');
+        $container.append(titleTextBoxFormGroup);
+    };
 }
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
index 66e91990f86074101ad93186f4f6c7aac001b285..f44630466dd4df1741ea821304dfa691d8dc1b08 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
@@ -271,7 +271,7 @@ def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, us
     titleField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
     titleField.set('mdschema', 'dc')
     titleField.set('element', 'title')
-    titleField.text = str(time.time())
+    titleField.text = ''
 
     typeField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
     typeField.set('mdschema', 'dc')
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties
index cc77ea6f8cfc5d2c91b4292696d89990276b2b7b..1e4e8fa3aa70869ce9c174f3bdcf7d2c5941224b 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/plugin.properties
@@ -2,5 +2,4 @@ label = Zenodo Exports API
 class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
 script-path = zenodoExports.py
 limit-data-size-megabytes=${zenodo-exports-api-limit-data-size-megabytes:4000}
-zenodoUrl=${zenodo-exports-api-zenodoUrl:https://localhost}
-accessToken=${zenodo-exports-api-accessToken}
\ No newline at end of file
+zenodoUrl=${zenodo-exports-api-zenodoUrl:https://localhost}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py
index 4934cac3586e661ffe31a3142a24b52af2876ba7..5922f0f8827de532880931ddc8150ef0501f88fc 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/zenodo-exports-api/zenodoExports.py
@@ -93,7 +93,7 @@ def export(entities, tr, params):
 def sendToZenodo(tr, params, tempZipFilePath, entities):
     depositRootUrl = str(getConfigurationProperty(tr, 'zenodoUrl')) + '/api/deposit/depositions'
 
-    accessToken = str(getConfigurationProperty(tr, 'accessToken'))
+    accessToken = params.get('accessToken')
     operationLog.info('accessToken: %s' % accessToken)
 
     httpClient = None
@@ -110,7 +110,7 @@ def sendToZenodo(tr, params, tempZipFilePath, entities):
         selfUrl = depositionLinks.get('self')
 
         submitFile(httpClient.newRequest(depositUrl), accessToken, tempZipFilePath)
-        addMetadata(httpClient.newRequest(selfUrl), accessToken)
+        addMetadata(params, httpClient.newRequest(selfUrl), accessToken)
 
         entityPermIds = map(lambda entity: entity['permId'], entities)
         zenodoCallable = ZenodoCallable(params, accessToken, selfUrl,
@@ -140,10 +140,10 @@ def submitFile(request, accessToken, tempZipFilePath):
     return JSONObject(contentStr)
 
 
-def addMetadata(request, accessToken):
+def addMetadata(params, request, accessToken):
     data = {
         'metadata': {
-            'title': str(time.time()),
+            'title': params.get('submissionTitle'),
             'license': 'cc-zero',
             'upload_type': 'dataset',
             'description': 'Add some description.',
@@ -225,7 +225,7 @@ class ZenodoCallable(object):
                     actionCompleted = True
                 elif publicationJson.get('submitted'):
                     operationLog.info('Publication #%d submitted. Registering metadata.' % publicationJson.get('id'))
-                    self.registerPublicationInOpenbis(publicationJson.get('metadata'))
+                    self.registerPublicationInOpenbis(publicationJson.get('metadata'), publicationJson.get('links'))
                     actionCompleted = True
                 else:
                     operationLog.info('Publication #%d not submitted yet.' % publicationJson.get('id'))
@@ -244,13 +244,13 @@ class ZenodoCallable(object):
         return actionCompleted
 
 
-    def registerPublicationInOpenbis(self, publicationMetadataJson):
+    def registerPublicationInOpenbis(self, publicationMetadataJson, publicationLinksJson):
         sessionToken = self.params.get('sessionToken')
         v3 = ServiceProvider.getV3ApplicationService()
         id = CustomASServiceCode('publication-api')
         options = CustomASServiceExecutionOptions() \
             .withParameter('method', 'insertPublication') \
-            .withParameter('publicationURL', self.selfUrl) \
+            .withParameter('publicationURL', publicationLinksJson.get('doi')) \
             .withParameter('openBISRelatedIdentifiers', self.permIdsStr) \
             .withParameter('name', publicationMetadataJson.get('title')) \
             .withParameter('publicationDescription', publicationMetadataJson.get('description')) \
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
index 9806b27718bb3855c9f0eff450b44c2edb637b3b..1f324a67b25e1605aa2cc5c1ec4fde6c329c2088 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/entrypoint.py
@@ -3,10 +3,10 @@ import os
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.operation import SynchronousOperationExecutionOptions
 from ch.systemsx.cisd.common.exceptions import UserFailureException
 from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
-from parsers import get_creations_from, get_definitions_from, get_creation_metadata_from, \
+from parsers import get_creations_from, get_definitions_from_xls, get_definitions_from_csv, get_creation_metadata_from, \
     CreationOrUpdateToOperationParser, versionable_types
 from processors import OpenbisDuplicatesHandler, PropertiesLabelHandler, DuplicatesHandler, \
-    unify_properties_representation_of
+    unify_properties_representation_of, validate_creations
 from search_engines import SearchEngine
 from utils import FileHandler
 from utils.openbis_utils import get_version_name_for, get_metadata_name_for
@@ -14,9 +14,9 @@ from utils.openbis_utils import get_version_name_for, get_metadata_name_for
 REMOVE_VERSIONS = False
 
 
-def validate_data(xls_byte_arrays, update_mode, xls_name):
-    if xls_byte_arrays is None:
-        raise UserFailureException('Excel sheet has not been provided. "xls" parameter is None')
+def validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name):
+    if xls_byte_arrays is None and csv_strings is None:
+        raise UserFailureException('Nor Excel sheet nor csv has not been provided. "xls" and "csv" parameters are None')
     if update_mode not in ['IGNORE_EXISTING', 'FAIL_IF_EXISTS', 'UPDATE_IF_EXISTS']:
         raise UserFailureException(
             'Update mode has to be one of following: IGNORE_EXISTING FAIL_IF_EXISTS UPDATE_IF_EXISTS but was ' + (
@@ -69,12 +69,15 @@ def process(context, parameters):
     search_engine = SearchEngine(api, session_token)
 
     xls_byte_arrays = parameters.get('xls', None)
+    csv_strings = parameters.get('csv', None)
     xls_name = parameters.get('xls_name', None)
     scripts = parameters.get('scripts', {})
     update_mode = parameters.get('update_mode', None)
-    validate_data(xls_byte_arrays, update_mode, xls_name)
-    definitions = get_definitions_from(xls_byte_arrays)
+    validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name)
+    definitions = get_definitions_from_xls(xls_byte_arrays)
+    definitions.extend(get_definitions_from_csv(csv_strings))
     creations = get_creations_from(definitions, FileHandler(scripts))
+    validate_creations(creations)
     creations_metadata = get_creation_metadata_from(definitions)
     creations = DuplicatesHandler.get_distinct_creations(creations)
     xls_version_filepath = get_property("xls-import.version-data-file", "../../../xls-import-version-info.json")
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
index 28a31b2287a5d71b2edf9d0be2aea7bde976f2c5..4bd91747064726fa6a71e79d8327ae2396c265d0 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/__init__.py
@@ -7,8 +7,9 @@ from .definition_to_creation import PropertyTypeDefinitionToCreationType, Vocabu
     SampleDefinitionToCreationType, ScriptDefinitionToCreationType
 from .definition_to_creation_metadata import DefinitionToCreationMetadataParser
 from .excel_to_poi import ExcelToPoiParser
-from .parsers_facade import get_creations_from, get_definitions_from, get_creation_metadata_from
-from .poi_to_definition import PoiToDefinitionParser, Definition
+from .parsers_facade import get_creations_from, get_definitions_from_xls, get_definitions_from_csv, \
+    get_creation_metadata_from
+from .to_definition import PoiToDefinitionParser, CsvReaderToDefinitionParser, Definition
 from .creation_to_update import CreationToUpdateParser, UpdateTypes, PropertyTypeCreationToUpdateType, \
     VocabularyCreationToUpdateType, VocabularyTermCreationToUpdateType, PropertyAssignmentCreationToUpdateType, \
     SampleTypeCreationToUpdateType, ExperimentTypeCreationToUpdateType, DatasetTypeCreationToUpdateType, \
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
index 51624c520c9fe2e88f51958a7843a7ed19e2973c..a2884641bee4bb0542245d2dec6b2ff9adf7d82e 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/parsers_facade.py
@@ -1,21 +1,29 @@
 from .definition_to_creation import DefinitionToCreationParser
 from .excel_to_poi import ExcelToPoiParser
-from .poi_to_definition import PoiToDefinitionParser
-
+from .to_definition import PoiToDefinitionParser, CsvReaderToDefinitionParser
 from .definition_to_creation_metadata import DefinitionToCreationMetadataParser
 
 
-def get_definitions_from(xls_byte_arrays):
+def get_definitions_from_xls(xls_byte_arrays):
     definitions = []
-    for excel_byte_array in xls_byte_arrays:
+    for excel_byte_array in xls_byte_arrays or []:
         poi_definitions = ExcelToPoiParser.parse(excel_byte_array)
         partial_definitions = PoiToDefinitionParser.parse(poi_definitions)
         definitions.extend(partial_definitions)
     return definitions
 
 
+def get_definitions_from_csv(csv_strings):
+    definitions = []
+    for csv_string in csv_strings or []:
+        csv_definitions = CsvReaderToDefinitionParser.parse(csv_string)
+        partial_definitions = PoiToDefinitionParser.parse(csv_definitions)
+        definitions.extend(partial_definitions)
+
+    return definitions
+
+
 def get_creations_from(definitions, context):
-    pass
     return DefinitionToCreationParser.parse(definitions, context)
 
 
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py
deleted file mode 100644
index 3d310af376a0dde1f2137448d01b8829d2dba457..0000000000000000000000000000000000000000
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .poi_to_definition import PoiToDefinitionParser
-from .definition import Definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c359051546a35c60c8bf3d4aa30c97480627876
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/__init__.py
@@ -0,0 +1,3 @@
+from .poi_to_definition.poi_to_definition import PoiToDefinitionParser
+from .csv_to_definition.csv_to_definition import CsvReaderToDefinitionParser
+from .definition import Definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb3fcffad0777bab6eb4c1c273d78f24358a992e
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/csv_to_definition/csv_to_definition.py
@@ -0,0 +1,28 @@
+from StringIO import StringIO
+import csv
+
+
+class CsvReaderToDefinitionParser():
+
+    @staticmethod
+    def parse(csv_string):
+        def is_row_empty(row):
+            return not ''.join(row).strip()
+
+        f = StringIO("".join(map(chr, csv_string)))
+        reader = csv.reader(f, delimiter=',')
+        definitions = []
+        definition_rows = []
+        previous_row_empty = False
+        for row in reader:
+            if is_row_empty(row) and previous_row_empty:
+                break
+            if is_row_empty(row):
+                definitions.append(definition_rows)
+                definition_rows = []
+                previous_row_empty = True
+                continue
+            previous_row_empty = False
+            definition_rows.append({k: v for k, v in enumerate(row) if v != ''})
+
+        return definitions
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/definition.py
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/definition.py
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
similarity index 66%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
index d17255c0f85deeb23f734f17ccd72eaf8ef9e499..5235edd7758f33af526098cbf1c98257b6d8542e 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/definition_parsers.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/definition_parsers.py
@@ -1,18 +1,20 @@
 from java.lang import UnsupportedOperationException
-from .definition import Definition
+from ..definition import Definition
 from .poi_cleaner import PoiCleaner
 
 
 class DefinitionParserFactory(object):
 
-        @staticmethod
-        def get_parser(definition_type):
-            if definition_type in ['VOCABULARY_TYPE', 'SAMPLE_TYPE', 'EXPERIMENT_TYPE', 'DATASET_TYPE', 'EXPERIMENT', 'SAMPLE']:
-                return GeneralDefinitionParser
-            elif definition_type in ['PROPERTY_TYPE', 'SPACE', 'PROJECT']:
-                return PropertiesOnlyDefinitionParser
-            else:
-                raise UnsupportedOperationException("Definition of " + str(definition_type) + " is not supported (probably yet).")
+    @staticmethod
+    def get_parser(definition_type):
+        if definition_type in ['VOCABULARY_TYPE', 'SAMPLE_TYPE', 'EXPERIMENT_TYPE', 'DATASET_TYPE', 'EXPERIMENT',
+                               'SAMPLE']:
+            return GeneralDefinitionParser
+        elif definition_type in ['PROPERTY_TYPE', 'SPACE', 'PROJECT']:
+            return PropertiesOnlyDefinitionParser
+        else:
+            raise UnsupportedOperationException(
+                "Definition of " + str(definition_type) + " is not supported (probably yet).")
 
 
 class PropertiesOnlyDefinitionParser(object):
@@ -25,12 +27,12 @@ class PropertiesOnlyDefinitionParser(object):
         PROPERTIES_VALUES_ROW_START = 2
 
         row_numbers = {
-            'DEFINITION_TYPE_ROW' : DEFINITION_TYPE_ROW,
-            'DEFINITION_TYPE_CELL' : DEFINITION_TYPE_CELL,
-            'ATTRIBUTES_HEADER_ROW' : None,
-            'ATTRIBUTES_VALUES_ROW' : None,
-            'PROPERTIES_HEADER_ROW' : PROPERTIES_HEADER_ROW,
-            'PROPERTIES_VALUES_ROW_START' : PROPERTIES_VALUES_ROW_START
+            'DEFINITION_TYPE_ROW': DEFINITION_TYPE_ROW,
+            'DEFINITION_TYPE_CELL': DEFINITION_TYPE_CELL,
+            'ATTRIBUTES_HEADER_ROW': None,
+            'ATTRIBUTES_VALUES_ROW': None,
+            'PROPERTIES_HEADER_ROW': PROPERTIES_HEADER_ROW,
+            'PROPERTIES_VALUES_ROW_START': PROPERTIES_VALUES_ROW_START
         }
 
         poi_definition = PoiCleaner.clean_data(poi_definition, row_numbers)
@@ -65,12 +67,12 @@ class GeneralDefinitionParser(object):
         PROPERTIES_VALUES_ROW_START = 4
 
         row_numbers = {
-            'DEFINITION_TYPE_ROW' : DEFINITION_TYPE_ROW,
-            'DEFINITION_TYPE_CELL' : DEFINITION_TYPE_CELL,
-            'ATTRIBUTES_HEADER_ROW' : ATTRIBUTES_HEADER_ROW,
-            'ATTRIBUTES_VALUES_ROW' : ATTRIBUTES_VALUES_ROW,
-            'PROPERTIES_HEADER_ROW' : PROPERTIES_HEADER_ROW,
-            'PROPERTIES_VALUES_ROW_START' : PROPERTIES_VALUES_ROW_START
+            'DEFINITION_TYPE_ROW': DEFINITION_TYPE_ROW,
+            'DEFINITION_TYPE_CELL': DEFINITION_TYPE_CELL,
+            'ATTRIBUTES_HEADER_ROW': ATTRIBUTES_HEADER_ROW,
+            'ATTRIBUTES_VALUES_ROW': ATTRIBUTES_VALUES_ROW,
+            'PROPERTIES_HEADER_ROW': PROPERTIES_HEADER_ROW,
+            'PROPERTIES_VALUES_ROW_START': PROPERTIES_VALUES_ROW_START
         }
 
         poi_definition = PoiCleaner.clean_data(poi_definition, row_numbers)
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
similarity index 90%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
index df03358a84096673c70b8c78d9352625a4f1745f..e4698a6c51da7c8e5b3bc29934cb63f7024fc1c4 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_cleaner.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_cleaner.py
@@ -25,13 +25,14 @@ class PoiCleaner(object):
                    If there's no corresponding value, None is inserted.
             4. Headers to lowercase
         '''
-        definition[DEFINITION_TYPE_ROW] = {0:definition[DEFINITION_TYPE_ROW][DEFINITION_TYPE_CELL]}
+        definition[DEFINITION_TYPE_ROW] = {0: definition[DEFINITION_TYPE_ROW][DEFINITION_TYPE_CELL]}
 
         if ATTRIBUTES_HEADER_ROW is not None:
             PoiCleaner.delete_empty_cells_from(definition, ATTRIBUTES_HEADER_ROW)
             if ATTRIBUTES_VALUES_ROW is not None:
                 PoiCleaner.delete_cells_if_no_header(definition, ATTRIBUTES_HEADER_ROW, ATTRIBUTES_VALUES_ROW)
-                PoiCleaner.create_cells_if_no_value_but_header_exists(definition, ATTRIBUTES_HEADER_ROW, ATTRIBUTES_VALUES_ROW)
+                PoiCleaner.create_cells_if_no_value_but_header_exists(definition, ATTRIBUTES_HEADER_ROW,
+                                                                      ATTRIBUTES_VALUES_ROW)
             definition[ATTRIBUTES_HEADER_ROW] = PoiCleaner.dict_values_to_lowercase(definition[ATTRIBUTES_HEADER_ROW])
 
         if PROPERTIES_HEADER_ROW is not None and PoiCleaner.hasProperties(definition, PROPERTIES_HEADER_ROW):
@@ -39,7 +40,8 @@ class PoiCleaner(object):
             if PROPERTIES_VALUES_ROW_START is not None:
                 for property_value_row_num in range(PROPERTIES_VALUES_ROW_START, len(definition)):
                     PoiCleaner.delete_cells_if_no_header(definition, PROPERTIES_HEADER_ROW, property_value_row_num)
-                    PoiCleaner.create_cells_if_no_value_but_header_exists(definition, PROPERTIES_HEADER_ROW, property_value_row_num)
+                    PoiCleaner.create_cells_if_no_value_but_header_exists(definition, PROPERTIES_HEADER_ROW,
+                                                                          property_value_row_num)
             definition[PROPERTIES_HEADER_ROW] = PoiCleaner.dict_values_to_lowercase(definition[PROPERTIES_HEADER_ROW])
 
         return definition
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_to_definition.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_to_definition.py
similarity index 100%
rename from openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/poi_to_definition/poi_to_definition.py
rename to openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/to_definition/poi_to_definition/poi_to_definition.py
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/__init__.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/__init__.py
index 4bf02ed2f60853c45dde046f96961ab7b0a0f090..7c32a00104eb924efb381e5a786c4149a3bf0c7a 100644
--- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/__init__.py
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/__init__.py
@@ -3,3 +3,4 @@ from .duplicates_handler import DuplicatesHandler
 from .properties_label_handler import PropertiesLabelHandler
 from .representation_unifier import unify_properties_representation_of
 from .version_handler import VersionHandler
+from .creation_validator import validate_creations
diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/creation_validator.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/creation_validator.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca81778bffb892efc715553c606fea9de6290ff6
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/processors/creation_validator.py
@@ -0,0 +1,49 @@
+from parsers import PropertyTypeDefinitionToCreationType
+from ch.systemsx.cisd.common.exceptions import UserFailureException
+
+
+def validate_creations(creations):
+    '''
+        This validator checks for:
+         - whether property types with same code are all the same across xls
+         - If allowed entries align with creations about to be made
+
+        It throws an exception if this is false.
+    '''
+    if PropertyTypeDefinitionToCreationType not in creations:
+        return
+    property_type_creations = creations[PropertyTypeDefinitionToCreationType]
+    different_duplicates = {}
+    for property_type in property_type_creations:
+        for second_property_type in property_type_creations:
+            if property_type.code == second_property_type.code:
+                difference_info = {}
+                attributes_to_check = ['label', 'description', 'dataType', 'internalNameSpace', 'vocabularyId',
+                                       'metaData']
+                for attribute in attributes_to_check:
+                    attribute_of_property_type = getattr(property_type, attribute)
+                    attribute_of_second_property_type = getattr(second_property_type, attribute)
+                    if attribute_of_property_type != attribute_of_second_property_type:
+                        if attribute not in difference_info:
+                            difference_info[attribute] = []
+                        attribute_pair = [str(attribute_of_property_type), str(attribute_of_second_property_type)]
+                        difference_info[attribute].extend(attribute_pair)
+
+                if difference_info:
+                    if property_type.code not in different_duplicates:
+                        different_duplicates[property_type.code] = {}
+                    for key in difference_info:
+                        if key not in different_duplicates[property_type.code]:
+                            different_duplicates[property_type.code][key] = set(difference_info[key])
+                        else:
+                            different_duplicates[property_type.code][key].update(difference_info[key])
+
+    if different_duplicates:
+        error_msg = "Following property types have ambiguous definition: \n" + \
+                    '\n'.join(['Property Code: ' + str(code) + '\n' + '\n'.join(
+                        ["Attribute with difference: " + attr_name + ", has following values: " + '\n' + ', '.join(
+                            ['"' + str(attribute_value) + '"' for attribute_value in duplicates_attributes])
+                         for attr_name, duplicates_attributes in diff_info.items()]) for code, diff_info in
+                               different_duplicates.items()]) + '\n'
+
+        raise UserFailureException(error_msg)
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
index 19f8b06059efe9c0d1d97a20d76b14fe96513572..193174e4d5c77a34c4cc2f2c2f21abe1f62f9ecd 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java
@@ -2,35 +2,52 @@ package ch.ethz.sis.openbis.systemtest.plugin.excelimport;
 
 import java.io.File;
 
+import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
 import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
 import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.coreplugin.CorePluginsUtils;
 
-public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests
-{
-
-    protected String FILES_DIR;
+public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests {
 
     private String XLS_VERSIONING_DIR = "xls-import.version-data-file";
 
+    private static final String TEST_USER = "test";
+
+    private static final String PASSWORD = "password";
+
+    @Autowired
+    protected IApplicationServerInternalApi v3api;
+
+    protected String sessionToken;
+
+    protected String FILES_DIR;
+
     @BeforeSuite
-    public void setupSuite()
-    {
+    public void setupSuite() {
         System.setProperty(XLS_VERSIONING_DIR, "./versioning.bin");
         System.setProperty(CorePluginsUtils.CORE_PLUGINS_FOLDER_KEY, "dist/core-plugins");
         System.setProperty(Constants.ENABLED_MODULES_KEY, "xls-import");
         TestInitializer.initEmptyDbNoIndex();
     }
 
+    @BeforeMethod
+    public void beforeTest() {
+        sessionToken = v3api.login(TEST_USER, PASSWORD);
+        System.out.println("AHAHHAHAHAHHA");
+        System.out.println(sessionToken);
+    }
+
     @AfterMethod
-    public void afterTest()
-    {
+    public void afterTest() {
         File f = new File("./versioning.bin");
         f.delete();
+        v3api.logout(sessionToken);
     }
 
 }
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java
index b65c8206c03512db46bc793195edd20975cf9ed0..d042fb24fe8c7906fc9b9c42dbadef71e81cb402 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java
@@ -15,6 +15,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -28,15 +29,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportDatasetTypesTest extends AbstractImportTest
-{
-
-    @Autowired
-    private IApplicationServerInternalApi v3api;
-
-    private static final String TEST_USER = "test";
-
-    private static final String PASSWORD = "password";
+public class ImportDatasetTypesTest extends AbstractImportTest {
 
     private static final String DATASET_TYPES_XLS = "dataset_types/normal_dataset.xls";
 
@@ -50,25 +43,15 @@ public class ImportDatasetTypesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportDatasetTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportDatasetTypesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testNormalDatasetTypesAreCreated() throws Exception
-    {
+    public void testNormalDatasetTypesAreCreated() throws Exception {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS)));
         // WHEN
@@ -98,8 +81,7 @@ public class ImportDatasetTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testDatasetTypesWithoutPropertiesTypesAreCreated() throws IOException
-    {
+    public void testDatasetTypesWithoutPropertiesTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_WITHOUT_PROPERTIES)));
         // WHEN
@@ -111,8 +93,7 @@ public class ImportDatasetTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testDatasetTypesWithValidationScript() throws Exception
-    {
+    public void testDatasetTypesWithValidationScript() throws Exception {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(),
                 Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_WITH_VALIDATION_SCRIPT)));
@@ -124,8 +105,7 @@ public class ImportDatasetTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testDatasetTypesUpdate() throws Exception
-    {
+    public void testDatasetTypesUpdate() throws Exception {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS)));
         // WHEN
@@ -155,8 +135,7 @@ public class ImportDatasetTypesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoSampleCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoSampleCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_NO_CODE)));
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java
index d0007539af55c929e69b82dcb3d1cb82b2b27b6f..4d96764ed4c66d3282e3ea1fe6ab721c2d0e5549 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java
@@ -15,6 +15,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -28,8 +29,8 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportExperimentTypesTest extends AbstractImportTest
-{
+public class ImportExperimentTypesTest extends AbstractImportTest {
+
     @Autowired
     private IApplicationServerInternalApi v3api;
 
@@ -47,25 +48,15 @@ public class ImportExperimentTypesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportExperimentTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportExperimentTypesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testNormalExperimentTypesAreCreated() throws Exception
-    {
+    public void testNormalExperimentTypesAreCreated() throws Exception {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS)));
         // WHEN
@@ -96,8 +87,7 @@ public class ImportExperimentTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentTypesUpdate() throws Exception
-    {
+    public void testExperimentTypesUpdate() throws Exception {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS)));
         // WHEN
@@ -113,9 +103,9 @@ public class ImportExperimentTypesTest extends AbstractImportTest
         assertFalse(nameProperty.isMandatory());
         assertTrue(nameProperty.isShowInEditView());
         assertEquals(nameProperty.getSection(), "General information");
+        assertEquals(nameProperty.getPropertyType().getDescription(), "NameUpdateDescription");
         assertEquals(nameProperty.getPropertyType().getLabel(), "NameUpdate");
         assertEquals(nameProperty.getPropertyType().getDataType(), DataType.VARCHAR);
-        assertEquals(nameProperty.getPropertyType().getDescription(), "NameUpdateDescription");
         assertEquals(nameProperty.getPlugin(), null);
         assertFalse(defaultObjectTypeProperty.isMandatory());
         assertTrue(defaultObjectTypeProperty.isShowInEditView());
@@ -129,8 +119,7 @@ public class ImportExperimentTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentTypesWithValidationScript() throws IOException
-    {
+    public void testExperimentTypesWithValidationScript() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(),
                 Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_WITH_VALIDATION_SCRIPT)));
@@ -141,8 +130,7 @@ public class ImportExperimentTypesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoSampleCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoSampleCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_NO_CODE)));
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java
index 55f21af7fac615b0d077d23562e13af4559ab896..dea9d2631bf587ace4e20e7a5e0f1c1705b65ba2 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java
@@ -11,6 +11,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -22,8 +23,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportExperimentsTest extends AbstractImportTest
-{
+public class ImportExperimentsTest extends AbstractImportTest {
     @Autowired
     private IApplicationServerInternalApi v3api;
 
@@ -67,25 +67,15 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportExperimentsTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportExperimentsTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreated() throws IOException
-    {
+    public void testExperimentsAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)));
         // WHEN
@@ -99,8 +89,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedSecondExperiment() throws IOException
-    {
+    public void testExperimentsAreCreatedSecondExperiment() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)));
         // WHEN
@@ -114,8 +103,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithEverythingOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWithEverythingOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
@@ -132,8 +120,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithEverythingOnServerAndInXls() throws IOException
-    {
+    public void testExperimentsAreCreatedWithEverythingOnServerAndInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
@@ -149,16 +136,14 @@ public class ImportExperimentsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfExperimentTypeDoesntExist() throws IOException
-    {
+    public void shouldThrowExceptionIfExperimentTypeDoesntExist() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfProjectDoesntExist() throws IOException
-    {
+    public void shouldThrowExceptionIfProjectDoesntExist() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE)));
@@ -166,8 +151,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithTypeOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWithTypeOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_ELSEWHERE)));
@@ -182,8 +166,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithTypeOnServerAndInXls() throws IOException
-    {
+    public void testExperimentsAreCreatedWithTypeOnServerAndInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
@@ -199,15 +182,13 @@ public class ImportExperimentsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfExperimentNoCode() throws IOException
-    {
+    public void shouldThrowExceptionIfExperimentNoCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_CODE)));
     }
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWhenNonMandatoryPropertiesAreNotProvided() throws IOException
-    {
+    public void testExperimentsAreCreatedWhenNonMandatoryPropertiesAreNotProvided() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_NON_MANDATORY_PROPERTY_MISSING)));
         // WHEN
@@ -220,15 +201,13 @@ public class ImportExperimentsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfExperimentNoProject() throws IOException
-    {
+    public void shouldThrowExceptionIfExperimentNoProject() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_PROJECT_ATTRIBUTE)));
     }
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithSpaceAndProjectOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWithSpaceAndProjectOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT)));
@@ -244,8 +223,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithSpaceOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWithSpaceOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_SPACE_ELSEWHERE)));
@@ -260,8 +238,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWithTypeAndSpaceOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWithTypeAndSpaceOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
@@ -276,15 +253,13 @@ public class ImportExperimentsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfMandatoryPropertyMissing() throws IOException
-    {
+    public void shouldThrowExceptionIfMandatoryPropertyMissing() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_MISSING)));
     }
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedIfMandatoryPropertyArePresent() throws IOException
-    {
+    public void testExperimentsAreCreatedIfMandatoryPropertyArePresent() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_PRESENT)));
         // WHEN
@@ -298,8 +273,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWhenPropertiesAreAddressedByLabelsWithTypeInXls() throws IOException
-    {
+    public void testExperimentsAreCreatedWhenPropertiesAreAddressedByLabelsWithTypeInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_PROPERTIES_COLUMNS_AS_LABELS)));
         // WHEN
@@ -313,8 +287,7 @@ public class ImportExperimentsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testExperimentsAreCreatedWhenPropertiesAreAddressedByLabelsWithTypeOnServer() throws IOException
-    {
+    public void testExperimentsAreCreatedWhenPropertiesAreAddressedByLabelsWithTypeOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)));
         TestUtils.createFrom(v3api, sessionToken,
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef83a7a2058334f7bf6bdf0213e070bc4d0cf9a2
--- /dev/null
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromCsvTest.java
@@ -0,0 +1,53 @@
+package ch.ethz.sis.openbis.systemtest.plugin.excelimport;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import static org.testng.Assert.assertNotNull;
+
+@ContextConfiguration(locations = "classpath:applicationContext.xml")
+@Transactional(transactionManager = "transaction-manager")
+@Rollback
+public class ImportFromCsvTest extends AbstractImportTest {
+
+    @Autowired
+    private IApplicationServerInternalApi v3api;
+
+    private static final String TEST_USER = "test";
+
+    private static final String PASSWORD = "password";
+
+    private static final String FULL_TYPES_CSV = "csv/types.csv";
+
+    @BeforeClass
+    public void setupClass() throws IOException {
+        String f = ImportExperimentTypesTest.class.getName().replace(".", "/");
+        FILES_DIR = f.substring(0, f.length() - ImportExperimentTypesTest.class.getSimpleName().length()) + "/test_files/";
+    }
+
+    @Test
+    @DirtiesContext
+    public void testNormalExperimentTypesAreCreated() throws Exception {
+        // GIVEN
+        TestUtils.createFromCsv(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, FULL_TYPES_CSV)));
+        // WHEN
+        Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION");
+        // THEN
+        assertNotNull(detection);
+    }
+
+
+}
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java
index 7b06530d87c6fa5575741a913414f78c8937692d..72f44c1a0ef59fc056560028be21af5c51e89734 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java
@@ -11,6 +11,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -22,8 +23,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportProjectsTest extends AbstractImportTest
-{
+public class ImportProjectsTest extends AbstractImportTest {
     @Autowired
     private IApplicationServerInternalApi v3api;
 
@@ -45,25 +45,15 @@ public class ImportProjectsTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportProjectsTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportProjectsTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testProjectsAreCreated() throws IOException
-    {
+    public void testProjectsAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_XLS)));
         // WHEN
@@ -76,8 +66,7 @@ public class ImportProjectsTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testProjectsAreCreatedSecondProject() throws IOException
-    {
+    public void testProjectsAreCreatedSecondProject() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_XLS)));
         // WHEN
@@ -89,15 +78,13 @@ public class ImportProjectsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoProjectCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoProjectCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_CODE)));
     }
 
     @Test
     @DirtiesContext
-    public void testProjectsAreCreatedNoDescription() throws IOException
-    {
+    public void testProjectsAreCreatedNoDescription() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_DESCRIPTION)));
         // WHEN
@@ -109,15 +96,13 @@ public class ImportProjectsTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoProjectSpace() throws IOException
-    {
+    public void shouldThrowExceptionIfNoProjectSpace() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_SPACE)));
     }
 
     @Test
     @DirtiesContext
-    public void testProjectsAreCreatedSpaceOnServer() throws IOException
-    {
+    public void testProjectsAreCreatedSpaceOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_WITH_SPACES_ON_SERVER)));
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
index 8b0def35d27e07b085af53fe2dce981ace80eb7d..7db73a88f35d6717cc265e4b5ac14f73c75a1f3f 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java
@@ -14,6 +14,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -26,8 +27,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportPropertyTypesTest extends AbstractImportTest
-{
+public class ImportPropertyTypesTest extends AbstractImportTest {
 
     @Autowired
     private IApplicationServerInternalApi v3api;
@@ -50,6 +50,10 @@ public class ImportPropertyTypesTest extends AbstractImportTest
 
     private static final String PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE = "property_types/vocabcode_when_not_vocabtype.xls";
 
+    private static final String PROPERTY_DUPLICATES_DIFFERENT = "property_types/duplicates_different.xls";
+
+    private static final String PROPERTY_TYPES_DUPLICATES_SAME = "property_types/duplicates_same.xls";
+
     private static final String PROPERTY_VOCABULARY_ON_SERVER = "property_types/with_vocab_on_server.xls";
 
     private static final String PROPERTY_VOCAB_TYPE = "property_types/with_vocab.xls";
@@ -58,25 +62,15 @@ public class ImportPropertyTypesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportPropertyTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportPropertyTypesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testNormalPropertyTypesAreCreated() throws IOException
-    {
+    public void testNormalPropertyTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)));
         // WHEN
@@ -93,8 +87,7 @@ public class ImportPropertyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testInternalPropertyTypesAreCreated() throws IOException
-    {
+    public void testInternalPropertyTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)));
         // WHEN
@@ -109,39 +102,55 @@ public class ImportPropertyTypesTest extends AbstractImportTest
         assertNull(notes.getVocabulary());
     }
 
+    @Test
+    @DirtiesContext
+    public void testDuplicatesPropertiesAreAllowedIfTheyAreTheSame() throws IOException {
+        // GIVEN
+        TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_DUPLICATES_SAME)));
+        // WHEN
+        PropertyType notes = TestUtils.getPropertyType(v3api, sessionToken, "NOTES");
+        // THEN
+        assertEquals(notes.getCode(), "NOTES");
+        assertEquals(notes.getLabel(), "Notes");
+        assertEquals(notes.getDataType(), DataType.MULTILINE_VARCHAR);
+        assertEquals(notes.getDescription(), "Notes Descripton");
+        assertFalse(notes.isInternalNameSpace());
+        assertFalse(notes.isManagedInternally());
+        assertNull(notes.getVocabulary());
+    }
+
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeNoCode() throws IOException
-    {
+    public void testPropertyTypeNoCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_CODE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeNoLabel() throws IOException
-    {
+    public void testPropertyTypeNoLabel() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_LABEL)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeNoVocabularyCodeWhenVocabularyType() throws IOException
-    {
+    public void testPropertyTypesDuplicatesAreDifferent() throws IOException {
+        TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_DUPLICATES_DIFFERENT)));
+    }
+
+    @Test(expectedExceptions = UserFailureException.class)
+    public void testPropertyTypeNoVocabularyCodeWhenVocabularyType() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_VOCAB_TYPE_NO_VOCABULARY_CODE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeNoDataType() throws IOException
-    {
+    public void testPropertyTypeNoDataType() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DATA_TYPE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeNoDescription() throws IOException
-    {
+    public void testPropertyTypeNoDescription() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DESCRIPTION)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void testPropertyTypeVocabularyCodeToNonVocabularyType() throws IOException
-    {
+    public void testPropertyTypeVocabularyCodeToNonVocabularyType() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE)));
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java
index 78c3538bf5c6fcc708c07b90dfd5d8c569b93112..9dc77235e4fbe810f07c1c4ea99d6bef16fa1a88 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java
@@ -14,6 +14,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -28,8 +29,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportSampleTypesTest extends AbstractImportTest
-{
+public class ImportSampleTypesTest extends AbstractImportTest {
 
     private static final String SAMPLE_TYPES_XLS = "sample_types/normal_samples.xls";
 
@@ -58,25 +58,15 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportSampleTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportSampleTypesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testNormalSampleTypesAreCreated() throws IOException
-    {
+    public void testNormalSampleTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)));
         // WHEN
@@ -87,8 +77,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testPropertyTypeAssignmentsFromNormalSampleTypesAreCreated() throws IOException
-    {
+    public void testPropertyTypeAssignmentsFromNormalSampleTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)));
         // WHEN
@@ -111,8 +100,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testPropertyTypeAssignmentsFromNormalv2SampleTypesAreCreated() throws IOException
-    {
+    public void testPropertyTypeAssignmentsFromNormalv2SampleTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS_DIFFERENT_PROPERTY_ASSIGN)));
         // WHEN
@@ -129,8 +117,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testPropertyTypesFromNormalSampleTypesAreCreated() throws IOException
-    {
+    public void testPropertyTypesFromNormalSampleTypesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)));
         // WHEN
@@ -151,8 +138,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSampleTypesWithPropertyHavingDynamicScript() throws IOException
-    {
+    public void testSampleTypesWithPropertyHavingDynamicScript() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, TestUtils.getDynamicPluginMap(),
                 Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_DYNAMIC_SCRIPT)));
@@ -168,8 +154,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSampleTypesWithPropertyHavingValidationScript() throws IOException
-    {
+    public void testSampleTypesWithPropertyHavingValidationScript() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(),
                 Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VALIDATION_SCRIPT)));
@@ -185,8 +170,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSampleTypesWithVocabularyInXls() throws IOException
-    {
+    public void testSampleTypesWithVocabularyInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY)));
         // WHEN
@@ -199,8 +183,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSampleTypesWithVocabularyOnServer() throws IOException
-    {
+    public void testSampleTypesWithVocabularyOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_DETECTION)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY_ON_SERVER)));
@@ -214,8 +197,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSampleTypesWithAutoGeneratedCodeAttribute() throws IOException
-    {
+    public void testSampleTypesWithAutoGeneratedCodeAttribute() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_AUTO_GENERATED_CODES)));
         // WHEN
@@ -225,8 +207,7 @@ public class ImportSampleTypesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoSampleCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoSampleCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_NO_CODE)));
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java
index f83fb7839c5e85bc3e832ed6d54b4e3ee3802752..da630f96e445df3ee1cc0bfc36e84f682d22cb45 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java
@@ -12,6 +12,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -23,8 +24,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportSamplesTest extends AbstractImportTest
-{
+public class ImportSamplesTest extends AbstractImportTest {
     @Autowired
     private IApplicationServerInternalApi v3api;
 
@@ -64,25 +64,15 @@ public class ImportSamplesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportSamplesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportSamplesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testSamplesAreCreated() throws IOException
-    {
+    public void testSamplesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)));
         // WHEN
@@ -96,8 +86,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedSecondSample() throws IOException
-    {
+    public void testSamplesAreCreatedSecondSample() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)));
         // WHEN
@@ -111,8 +100,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedThirdSample() throws IOException
-    {
+    public void testSamplesAreCreatedThirdSample() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)));
         // WHEN
@@ -126,8 +114,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedWhenSpaceOnServer() throws IOException
-    {
+    public void testSamplesAreCreatedWhenSpaceOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)));
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)));
@@ -139,8 +126,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedWhenSpaceInSeparateXls() throws IOException
-    {
+    public void testSamplesAreCreatedWhenSpaceInSeparateXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)),
@@ -152,15 +138,13 @@ public class ImportSamplesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfSpaceDoesntExist() throws IOException
-    {
+    public void shouldThrowExceptionIfSpaceDoesntExist() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)));
     }
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedWhenSampleTypeOnServer() throws IOException
-    {
+    public void testSamplesAreCreatedWhenSampleTypeOnServer() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)),
@@ -174,8 +158,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesAreCreatedWhenSampleTypeInSeparateXls() throws IOException
-    {
+    public void testSamplesAreCreatedWhenSampleTypeInSeparateXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE)),
@@ -188,8 +171,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesChildrenAreAssignedWhenAddressedByCodeInXls() throws IOException
-    {
+    public void testSamplesChildrenAreAssignedWhenAddressedByCodeInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, CHILD_AS_CODE)));
@@ -205,8 +187,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesParentsAreAssignedWhenAddressedByCodeInXls() throws IOException
-    {
+    public void testSamplesParentsAreAssignedWhenAddressedByCodeInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, PARENT_AS_CODE)));
@@ -222,8 +203,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesChildrenAreAssignedWhenAddressedByDollartagInXls() throws IOException
-    {
+    public void testSamplesChildrenAreAssignedWhenAddressedByDollartagInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, CHILD_AS_DOLLARTAG)));
@@ -239,8 +219,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testSamplesParentsAreAssignedWhenAddressedByDollartagInXls() throws IOException
-    {
+    public void testSamplesParentsAreAssignedWhenAddressedByDollartagInXls() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, PARENT_AS_DOLLARTAG)));
@@ -256,8 +235,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testCreatesSampleWithNonMandatoryFieldsMissing() throws IOException
-    {
+    public void testCreatesSampleWithNonMandatoryFieldsMissing() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, NON_MANDATORY_FIELD_MISSING)));
@@ -270,8 +248,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testCreatesSampleWithAutogeneratedCodeWhenOnPerSampleLevel() throws IOException
-    {
+    public void testCreatesSampleWithAutogeneratedCodeWhenOnPerSampleLevel() throws IOException {
         // GIVEN
         String result = TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_LEVEL)));
@@ -285,8 +262,7 @@ public class ImportSamplesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testCreatesSampleWithAutogeneratedCodeWhenOnSampleTypeLevel() throws IOException
-    {
+    public void testCreatesSampleWithAutogeneratedCodeWhenOnSampleTypeLevel() throws IOException {
         // GIVEN
         String result = TestUtils.createFrom(v3api, sessionToken,
                 Paths.get(FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_TYPE_LEVEL)));
@@ -299,14 +275,12 @@ public class ImportSamplesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfSamplesSpaceProjectDoesntExist() throws IOException
-    {
+    public void shouldThrowExceptionIfSamplesSpaceProjectDoesntExist() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_PROJECT_EXPERIMENT_ELSEWHERE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfMandatoryPropertyIsMissing() throws IOException
-    {
+    public void shouldThrowExceptionIfMandatoryPropertyIsMissing() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, MANDATORY_FIELD_MISSING)));
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java
index c76bf95b2505429d0711af6453ffebf7dbdd9fc8..fbf983dcbe02e05d2cf054323c3d9622fc0415a0 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java
@@ -11,6 +11,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -22,8 +23,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportSpacesTest extends AbstractImportTest
-{
+public class ImportSpacesTest extends AbstractImportTest {
     @Autowired
     private IApplicationServerInternalApi v3api;
 
@@ -39,25 +39,15 @@ public class ImportSpacesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportSpacesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportSpacesTest.class.getSimpleName().length()) + "/test_files/";
     }
 
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
-    }
-
     @Test
     @DirtiesContext
-    public void testNormalSpacesAreCreated() throws IOException
-    {
+    public void testNormalSpacesAreCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_XLS)));
         // WHEN
@@ -69,8 +59,7 @@ public class ImportSpacesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testNormalSpacesAreCreatedSecondSpace() throws IOException
-    {
+    public void testNormalSpacesAreCreatedSecondSpace() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_XLS)));
         // WHEN
@@ -81,15 +70,13 @@ public class ImportSpacesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoSpaceCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoSpaceCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_NO_CODE)));
     }
 
     @Test
     @DirtiesContext
-    public void shouldCreateSpaceWhenNoDescription() throws IOException
-    {
+    public void shouldCreateSpaceWhenNoDescription() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_NO_DESCRIPTION)));
         // WHEN
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
index 4a8f9088f44b3de91fbf0614bb0d1f088b6556d3..8b7778c786933c00dc0b483e14ac7ad0ffeefdd9 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java
@@ -30,6 +30,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.transaction.annotation.Transactional;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -42,8 +43,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 @ContextConfiguration(locations = "classpath:applicationContext.xml")
 @Transactional(transactionManager = "transaction-manager")
 @Rollback
-public class ImportVocabularyTypesTest extends AbstractImportTest
-{
+public class ImportVocabularyTypesTest extends AbstractImportTest {
 
     private static final String VOCABULARIES_TYPES_XLS = "vocabularies/normal_vocab.xls";
 
@@ -68,26 +68,15 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     private static String FILES_DIR;
 
-    private String sessionToken;
-
     @BeforeClass
-    public void setupClass() throws IOException
-    {
+    public void setupClass() throws IOException {
         String f = ImportVocabularyTypesTest.class.getName().replace(".", "/");
         FILES_DIR = f.substring(0, f.length() - ImportVocabularyTypesTest.class.getSimpleName().length()) + "/test_files/";
-
-    }
-
-    @BeforeMethod
-    public void beforeTest()
-    {
-        sessionToken = v3api.login(TEST_USER, PASSWORD);
     }
 
     @Test
     @DirtiesContext
-    public void testNormalVocabularyCreationIsCreated() throws IOException
-    {
+    public void testNormalVocabularyCreationIsCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)));
         // WHEN
@@ -99,8 +88,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testNormalVocabularyHasFirstTermCreated() throws IOException
-    {
+    public void testNormalVocabularyHasFirstTermCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)));
         // WHEN
@@ -114,8 +102,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testNormalVocabularyCreatedNoExtraVocabulary() throws IOException
-    {
+    public void testNormalVocabularyCreatedNoExtraVocabulary() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)));
         // WHEN
@@ -126,8 +113,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testNormalVocabularyHasSecondTermCreated() throws IOException
-    {
+    public void testNormalVocabularyHasSecondTermCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)));
         // WHEN
@@ -141,8 +127,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void testVocabularyWithNoTermDescriptionShouldBeCreated() throws IOException
-    {
+    public void testVocabularyWithNoTermDescriptionShouldBeCreated() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_DESCRIPTION)));
         // WHEN
@@ -153,21 +138,18 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoVocabularyCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoVocabularyCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_CODE)));
     }
 
     @Test(expectedExceptions = UserFailureException.class)
-    public void shouldThrowExceptionIfNoTermCode() throws IOException
-    {
+    public void shouldThrowExceptionIfNoTermCode() throws IOException {
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_CODE)));
     }
 
     @Test
     @DirtiesContext
-    public void shouldNotThrowExceptionIfNoVocabularyDescription() throws IOException
-    {
+    public void shouldNotThrowExceptionIfNoVocabularyDescription() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_DESCRIPTION)));
         // WHEN
@@ -179,8 +161,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void shouldNotThrowExceptionIfNoTermLabel() throws IOException
-    {
+    public void shouldNotThrowExceptionIfNoTermLabel() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_LABEL)));
         // WHEN
@@ -192,8 +173,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest
 
     @Test
     @DirtiesContext
-    public void shouldNotThrowExceptionIfNoTerms() throws IOException
-    {
+    public void shouldNotThrowExceptionIfNoTerms() throws IOException {
         // GIVEN
         TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERMS)));
         // WHEN
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
index 8991380e7946060be63d06291bb7f7c213121bc6..f81328e301bb1d1e93f76c71cf8592eb7f73e84a 100644
--- a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java
@@ -9,6 +9,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import org.apache.commons.io.IOUtils;
@@ -53,58 +54,51 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi;
 
-public class TestUtils
-{
+public class TestUtils {
     private static final String TEST_XLS = "TEST-XLS";
 
     private static final String XLS_NAME = "xls_name";
 
     private static final String UPDATE_MODE = "update_mode";
 
-    public static final String XLS_PARAM = "xls";
+    private static final String XLS_PARAM = "xls";
 
-    public static final String SCRIPTS_PARAM = "scripts";
+    public static final String CSV_PARAM = "csv";
 
-    public static final String XLS_IMPORT_API = "xls-import-api";
+    private static final String SCRIPTS_PARAM = "scripts";
 
-    static Vocabulary getVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    private static final String XLS_IMPORT_API = "xls-import-api";
+
+    static Vocabulary getVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         VocabularySearchCriteria criteria = new VocabularySearchCriteria();
         criteria.withId().thatEquals(new VocabularyPermId(code));
-
         VocabularyFetchOptions fo = new VocabularyFetchOptions();
         fo.withTerms();
 
         SearchResult<Vocabulary> result = v3api.searchVocabularies(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static List<Vocabulary> getAllVocabularies(IApplicationServerInternalApi v3api, String sessionToken)
-    {
+    static List<Vocabulary> getAllVocabularies(IApplicationServerInternalApi v3api, String sessionToken) {
         VocabularySearchCriteria criteria = new VocabularySearchCriteria();
         VocabularyFetchOptions fo = new VocabularyFetchOptions();
         fo.withTerms();
 
         SearchResult<Vocabulary> result = v3api.searchVocabularies(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects();
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static SampleType getSampleType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static SampleType getSampleType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         SampleTypeSearchCriteria criteria = new SampleTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -116,17 +110,14 @@ public class TestUtils
 
         SearchResult<SampleType> result = v3api.searchSampleTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static ExperimentType getExperimentType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static ExperimentType getExperimentType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         ExperimentTypeSearchCriteria criteria = new ExperimentTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -138,17 +129,14 @@ public class TestUtils
 
         SearchResult<ExperimentType> result = v3api.searchExperimentTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static DataSetType getDatasetType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static DataSetType getDatasetType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         DataSetTypeSearchCriteria criteria = new DataSetTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -160,17 +148,14 @@ public class TestUtils
 
         SearchResult<DataSetType> result = v3api.searchDataSetTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static PropertyType getPropertyType(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static PropertyType getPropertyType(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         PropertyTypeSearchCriteria criteria = new PropertyTypeSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -179,17 +164,14 @@ public class TestUtils
 
         SearchResult<PropertyType> result = v3api.searchPropertyTypes(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Space getSpace(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static Space getSpace(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         SpaceSearchCriteria criteria = new SpaceSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -197,17 +179,14 @@ public class TestUtils
 
         SearchResult<Space> result = v3api.searchSpaces(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Project getProject(IApplicationServerInternalApi v3api, String sessionToken, String code)
-    {
+    static Project getProject(IApplicationServerInternalApi v3api, String sessionToken, String code) {
         ProjectSearchCriteria criteria = new ProjectSearchCriteria();
         criteria.withCode().thatEquals(code);
 
@@ -216,18 +195,15 @@ public class TestUtils
 
         SearchResult<Project> result = v3api.searchProjects(sessionToken, criteria, fo);
 
-        if (result.getObjects().size() > 0)
-        {
+        if (result.getObjects().size() > 0) {
             return result.getObjects().get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
     static Experiment getExperiment(IApplicationServerInternalApi v3api, String sessionToken, String experimentCode, String projectCode,
-            String spaceCode)
-    {
+                                    String spaceCode) {
         List<IExperimentId> ids = new ArrayList<>();
         ids.add(new ExperimentIdentifier(spaceCode, projectCode, experimentCode));
 
@@ -238,33 +214,28 @@ public class TestUtils
 
         List<Experiment> result = v3api.getExperiments(sessionToken, ids, fo).values().stream().collect(Collectors.toList());
 
-        if (result.size() > 0)
-        {
+        if (result.size() > 0) {
             return result.get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String sampleCode, String spaceCode)
-    {
+    static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String sampleCode, String spaceCode) {
         List<ISampleId> ids = new ArrayList<>();
         ids.add(new SampleIdentifier(spaceCode, null, null, sampleCode));
 
         return getSamples(v3api, sessionToken, ids);
     }
 
-    static Sample getSampleByPermId(IApplicationServerInternalApi v3api, String sessionToken, String permId)
-    {
+    static Sample getSampleByPermId(IApplicationServerInternalApi v3api, String sessionToken, String permId) {
         List<ISampleId> ids = new ArrayList<>();
         ids.add(new SamplePermId(permId));
 
         return getSamples(v3api, sessionToken, ids);
     }
 
-    private static Sample getSamples(IApplicationServerInternalApi v3api, String sessionToken, List<ISampleId> ids)
-    {
+    private static Sample getSamples(IApplicationServerInternalApi v3api, String sessionToken, List<ISampleId> ids) {
         SampleFetchOptions fo = new SampleFetchOptions();
         SampleFetchOptions childrenFo = fo.withChildren();
         childrenFo.withSpace();
@@ -280,20 +251,16 @@ public class TestUtils
 
         List<Sample> result = v3api.getSamples(sessionToken, ids, fo).values().stream().collect(Collectors.toList());
 
-        if (result.size() > 0)
-        {
+        if (result.size() > 0) {
             return result.get(0);
-        } else
-        {
+        } else {
             return null;
         }
     }
 
-    static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Path... xls_paths) throws IOException
-    {
+    static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Path... xls_paths) throws IOException {
         List<byte[]> excels = new ArrayList<>();
-        for (Path xls_path : xls_paths)
-        {
+        for (Path xls_path : xls_paths) {
             byte[] xls = readData(xls_path);
             excels.add(xls);
         }
@@ -304,18 +271,28 @@ public class TestUtils
         return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
     }
 
+    static String createFromCsv(IApplicationServerInternalApi v3api, String sessionToken, Path... csv_paths) throws IOException {
+        List<byte[]> csvs = new ArrayList<>();
+        for (Path csv_path : csv_paths) {
+            byte[] csv = readData(csv_path);
+            csvs.add(csv);
+        }
+        CustomASServiceExecutionOptions options = new CustomASServiceExecutionOptions();
+        options.withParameter(CSV_PARAM, csvs);
+        options.withParameter(UPDATE_MODE, UpdateMode.IGNORE_EXISTING.name());
+        options.withParameter(XLS_NAME, TEST_XLS);
+        return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
+    }
+
     static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, Path... xls_paths)
-            throws IOException
-    {
+            throws IOException {
         return TestUtils.createFrom(v3api, sessionToken, scripts, UpdateMode.IGNORE_EXISTING, xls_paths);
     }
 
     static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, UpdateMode updateMode,
-            Path... xls_paths) throws IOException
-    {
+                             Path... xls_paths) throws IOException {
         List<byte[]> excels = new ArrayList<>();
-        for (Path xls_path : xls_paths)
-        {
+        for (Path xls_path : xls_paths) {
             byte[] xls = readData(xls_path);
             excels.add(xls);
         }
@@ -327,18 +304,15 @@ public class TestUtils
         return (String) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options);
     }
 
-    static String getValidationScript()
-    {
+    static String getValidationScript() {
         return "def validate(entity, isNew):\n  if isNew:\n    return";
     }
 
-    static String getDynamicScript()
-    {
+    static String getDynamicScript() {
         return "def calculate():\n    return 1";
     }
 
-    static Map<String, String> getValidationPluginMap()
-    {
+    static Map<String, String> getValidationPluginMap() {
         String dynamicScriptString = getValidationScript();
         Map<String, String> scriptsMap = new HashMap<>();
         scriptsMap.put("valid.py", dynamicScriptString);
@@ -346,8 +320,7 @@ public class TestUtils
         return scriptsMap;
     }
 
-    static Map<String, String> getDynamicPluginMap()
-    {
+    static Map<String, String> getDynamicPluginMap() {
         String dynamicScriptString = getDynamicScript();
         Map<String, String> scriptsMap = new HashMap<>();
         scriptsMap.put("dynamic/dynamic.py", dynamicScriptString);
@@ -355,8 +328,7 @@ public class TestUtils
         return scriptsMap;
     }
 
-    static String extractSamplePermIdFromResults(String result)
-    {
+    static String extractSamplePermIdFromResults(String result) {
         // Note this will work only if we created single sample!!
         String permId = result.substring(result.indexOf("CreateSamplesOperationResult") + "CreateSamplesOperationResult".length());
         permId = StringUtils.strip(permId, "[]");
@@ -364,15 +336,13 @@ public class TestUtils
     }
 
     static List<PropertyAssignment> extractAndSortPropertyAssignmentsPerGivenPropertyName(IEntityType rawData, List<String> propertyNames)
-            throws Exception
-    {
+            throws Exception {
         List<PropertyAssignment> propertyAssignments = rawData.getPropertyAssignments();
         List<PropertyAssignment> sortedPropertyAssignments = propertyNames.stream().map(propertyName -> {
             return propertyAssignments.stream().filter(prop -> prop.getPropertyType().getPermId().toString().equals(propertyName)).findFirst().get();
         }).collect(Collectors.toList());
 
-        if (sortedPropertyAssignments.stream().anyMatch(property -> property == null))
-        {
+        if (sortedPropertyAssignments.stream().anyMatch(Objects::isNull)) {
             throw new Exception("Some properties are missing"
                     + "\nFollowing properties are expected " + Arrays.toString(propertyNames.toArray())
                     + "\n Available properties are: " + Arrays.toString(propertyAssignments.toArray()));
@@ -381,21 +351,12 @@ public class TestUtils
         return sortedPropertyAssignments;
     }
 
-    private static byte[] readData(Path xls_path) throws IOException
-    {
+    private static byte[] readData(Path xls_path) throws IOException {
         String path = xls_path.toString();
-        InputStream resourceAsStream = TestUtils.class.getClassLoader().getResourceAsStream(path);
-        try
-        {
+        try (InputStream resourceAsStream = TestUtils.class.getClassLoader().getResourceAsStream(path)) {
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             IOUtils.copy(resourceAsStream, byteArrayOutputStream);
             return byteArrayOutputStream.toByteArray();
-        } finally
-        {
-            if (resourceAsStream != null)
-            {
-                resourceAsStream.close();
-            }
         }
     }
 
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv
new file mode 100644
index 0000000000000000000000000000000000000000..f316aaed6c9fbd4852819bfbdecfb0892bca93be
--- /dev/null
+++ b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/csv/types.csv
@@ -0,0 +1,7 @@
+VOCABULARY_TYPE,,,,,,,,,,,,,,,,,
+Version,Code,Description,,,,,,,,,,,,,,,
+225,DETECTION,Protein detection system,,,,,,,,,,,,,,,
+Version,Code,Label,Description,,,,,,,,,,,,,,
+225,HRP,horseradish peroxydase,The antibody is conjugated with the horseradish peroxydase,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment.xls b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment.xls
index 166c329684354e9de6cd843ebb64be76e7d42dd3..4d4d7fab58aaca4cfd9b45f3c386ddbd8e1e37b2 100644
Binary files a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment.xls and b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment.xls differ
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment_update.xls b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment_update.xls
index 6dadc7a18f04e1960f2ccac1f9ef22294637c5eb..fbf1ec66ae61acad2e326f21bb3a71cb5addc473 100644
Binary files a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment_update.xls and b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiment_types/normal_experiment_update.xls differ
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_different.xls b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_different.xls
new file mode 100644
index 0000000000000000000000000000000000000000..81f20402fd34580406f2b52dc078df22f1e712bf
Binary files /dev/null and b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_different.xls differ
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_same.xls b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_same.xls
new file mode 100644
index 0000000000000000000000000000000000000000..35b3eb68daf6ab65fbe014d77afe51c26311a8db
Binary files /dev/null and b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/duplicates_same.xls differ
diff --git a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls
index b60b0d1af6c8bcc493e04f7ab025c2f30bcba1b4..eb22dc6e62a54f32184abf1b8c37691847f17000 100644
Binary files a/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls and b/openbis_standard_technologies/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls differ