diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
index 589cda418e56d12632df7b22e665c188806fcab4..549b910bf1cfacc3167c41bb6a856fbcb365f5a3 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js
@@ -30,7 +30,7 @@ loadJSResorce("./etc/InstanceProfile.js", onLoadInstanceProfileResorceFunc);
 //</PROFILE_PLACEHOLDER>
 
 var PLUGINS_CONFIGURATION = {
-    extraPlugins : ["life-sciences", "flow", "microscopy"]
+    extraPlugins : ["life-sciences", "flow", "microscopy", "bbb-hub"]
 };
 
 var options = {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/PrintUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/PrintUtil.js
index 22821e3fb66c7337c2003db056c9fda3bc952795..1c6fba5b79e9c0c96b2b6c6fc988cc19b6a269ae 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/PrintUtil.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/PrintUtil.js
@@ -66,7 +66,7 @@ var PrintUtil = new function() {
 		var samplesListOfCodes = "";
 		
 		for(var sampleTypeCode in allSamplesByType) {
-			samplesListOfCodes += sampleTypeCode + ": ";
+			samplesListOfCodes += Util.getDisplayNameFromCode(sampleTypeCode) + ": ";
 			var samples = allSamplesByType[sampleTypeCode];
 			for(var i = 0; i < samples.length; i++) {
 				var sample = samples[i];
@@ -76,13 +76,7 @@ var PrintUtil = new function() {
 				if(sampleTypeCode === "STORAGE_POSITION") {
 					samplesListOfCodes += Util.getStoragePositionDisplayName(sample);
 				} else {
-					var name = sample.properties[profile.propertyReplacingCode];
-					if(!name) {
-						samplesListOfCodes += sample.code;
-					} else {
-						samplesListOfCodes += sample.code + "(" + name + ")";
-					}
-					
+					samplesListOfCodes += Util.getDisplayNameForEntity(sample); 
 				}
 			}
 			samplesListOfCodes += "</br>";
@@ -127,6 +121,7 @@ var PrintUtil = new function() {
 		
 		var $newInspectorTable = $("<table>", { "class" : "properties table table-condensed" });
 		$newInspector.append($newInspectorTable);
+		this._addLabelAndValue($newInspectorTable, "Code", entity.code);
 		
 		if(extraProperties) {
 			for(code in extraProperties) {
@@ -136,11 +131,7 @@ var PrintUtil = new function() {
 				if(propLabel.length > 25) {
 					propLabel = propLabel.substring(0, 23) + "..."; 
 				}
-				$newInspectorTable
-				.append($("<tr>")
-							.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append(propLabel + ":")))
-							.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak"}).append(extraProp.value)))
-						);
+				this._addLabelAndValue($newInspectorTable, propLabel, extraProp.value);
 			}
 		}
 		
@@ -197,11 +188,7 @@ var PrintUtil = new function() {
 									.append($("<td>", { "class" : "property", "colspan" : "2" }).append($("<p>", { "class" : "inspectorLineBreak"}).append(propertyLabel + ":").append("<br>").append(propertyContent)))
 								);
 					} else {
-						$newInspectorTable
-						.append($("<tr>")
-									.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append(propertyLabel + ":")))
-									.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak" }).append(propertyContent)))
-								);
+						this._addLabelAndValue($newInspectorTable, propertyLabel, propertyContent);
 					}
 				}
 			}
@@ -211,38 +198,22 @@ var PrintUtil = new function() {
 			//Show Parent Codes
 			var allParentCodesAsText = this._getCodesFromSamples(entity.parents);
 			if(allParentCodesAsText.length > 0) {
-				$newInspectorTable
-					.append($("<tr>")
-								.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append("Parents:")))
-								.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak" }).append(allParentCodesAsText)))
-							);
+				this._addLabelAndValue($newInspectorTable, "Parents", allParentCodesAsText);
 			}
 				
 			//Show Children Codes
 			var allChildrenCodesAsText = this._getCodesFromSamples(entity.children);
 			if(allChildrenCodesAsText.length > 0) {
-				$newInspectorTable
-				.append($("<tr>")
-							.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append("Children:")))
-							.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak"}).append(allChildrenCodesAsText)))
-						);
+				this._addLabelAndValue($newInspectorTable, "Children", allChildrenCodesAsText);
 			}
 		}
 		
 		//Show Modification Date
 		if(entity.registrationDetails) {
-			$newInspectorTable
-			.append($("<tr>")
-						.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append("Modification Date:")))
-						.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak"}).append(new Date(entity.registrationDetails["modificationDate"]))))
-					);
+			this._addLabelAndValue($newInspectorTable, "Modification Date", new Date(entity.registrationDetails["modificationDate"]));
 			
 			//Show Creation Date
-			$newInspectorTable
-			.append($("<tr>")
-						.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append("Registration Date:")))
-						.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak"}).append(new Date(entity.registrationDetails["registrationDate"]))))
-					);
+			this._addLabelAndValue($newInspectorTable, "Registration Date", new Date(entity.registrationDetails["registrationDate"]));
 		}
 		
 		if(extraCustomId && extraContent) {
@@ -251,6 +222,15 @@ var PrintUtil = new function() {
 		
 		return $newInspector;
 	};
+	
+	this._addLabelAndValue = function($newInspectorTable, label, value) {
+		$newInspectorTable
+		.append($("<tr>")
+					.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLabel"}).append(label + ":")))
+					.append($("<td>", { "class" : "property", "colspan" : "1" }).append($("<p>", { "class" : "inspectorLineBreak" }).append(value)))
+				);
+
+	}
 
 	this._convertJsonToHtml = function(json) {
 		data = json["data"];
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/HierarchyTableView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/HierarchyTableView.js
index 24877303093f678575a70a72f0291c1f4ad2584f..9835090f45922896464c9433814ad747f04a71cb 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/HierarchyTableView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/HierarchyTableView.js
@@ -32,14 +32,7 @@ function HierarchyTableView(controller, model) {
 		$containerColumn.append(this._container);
 		views.content.append($containerColumn);
 		
-		switch(this._model.entity["@type"]) {
-				case "as.dto.dataset.DataSet":
-					views.header.append($("<h1>").append("Dataset Hierarchy Table for " + this._model.entity.code));
-					break;
-				case "as.dto.sample.Sample":
-					views.header.append($("<h1>").append("" + ELNDictionary.Sample + " Hierarchy Table for " + this._model.entity.identifier));
-					break;
-		}
+		views.header.append($("<h1>").append("Dataset Hierarchy Table: " + Util.getDisplayNameForEntity(this._model.entity)));
 		
 		this._hierarchyFilterController = new HierarchyFilterController(this._model.entity, function() { _this._dataGrid.refresh(); });
 		this._hierarchyFilterController.init(views.header);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/widgets/HierarchyFilterView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/widgets/HierarchyFilterView.js
index 0b85ab4e57946aaad1d8ad1023dccc7ced7b7481..58b169b00e3891c19750ddaa9c95eb1d34b2062b 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/widgets/HierarchyFilterView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/HierarchyTable/widgets/HierarchyFilterView.js
@@ -41,7 +41,7 @@ function HierarchyFilterView(controller, model) {
 		var types = this._model.getTypes();
 		var $filtersFormEntityTypes = $('<select>', { 'id' : 'entityTypesSelector' , class : 'multiselect' , 'multiple' : 'multiple'});
 		for (var type in types) {
-			$filtersFormEntityTypes.append($('<option>', { 'value' : type , 'selected' : ''}).html(type));
+			$filtersFormEntityTypes.append($('<option>', { 'value' : type , 'selected' : ''}).html(Util.getDisplayNameFromCode(type)));
 		}
 		
 		$filtersForm
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
index 59b4cf6a5a771eea2162486a95c859f2c818a5a4..012e32325cc3a7bea388bda7d9bb95c10bda10d5 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/StorageManager/widgets/GridView.js
@@ -147,7 +147,6 @@ function GridView(gridModel) {
 						sample = jQuery.extend(true, {}, labels[i].data.samples[0]);
 					}
 					
-					extraProperties = null;
 					if(sample && sample.sampleTypeCode === "STORAGE_POSITION" && sample.parents && sample.parents[0]) {
 						if(profile.propertyReplacingCode &&  sample.parents[0].properties &&  sample.parents[0].properties[profile.propertyReplacingCode]) {
 							// Label
@@ -158,7 +157,6 @@ function GridView(gridModel) {
 						}
 						
 						sample = sample.parents[0];
-						extraProperties = {code : {label : "Code", value : sample.code}};
 							
 						var href = Util.getURLFor(null, "showViewSamplePageFromPermId", sample.permId);
 						optSampleTitle = $("<a>", { "href" : href, "class" : "browser-compatible-javascript-link" }).text(labels[i].displayName);
@@ -175,7 +173,7 @@ function GridView(gridModel) {
 					var labelContainer = $("<div>", { class: "storageBox", id : storageBoxId }).text(labels[i].displayName);
 					if (sample) {
 						var tooltip = PrintUtil.getTable(sample, false, optSampleTitle, 'inspectorWhiteFont', 
-								'colorEncodedWellAnnotations-holder-' + sample.permId, null, extraProperties);
+								'colorEncodedWellAnnotations-holder-' + sample.permId, null, null);
 						labelContainer.tooltipster({
 							content: $(tooltip),
 							interactive: true,
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/legacy/SampleHierarchy.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/legacy/SampleHierarchy.js
index b9dbfc5f1c39df96d9fdd55e2b3707ad23a0148b..f9cd29ef8287f469006da8acd8132a87ef68f358 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/legacy/SampleHierarchy.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/legacy/SampleHierarchy.js
@@ -43,7 +43,7 @@ function SampleHierarchy(serverFacade, views, profile, sample) {
 		$form.append($formColumn);
 		
 		
-		views.header.append($("<h1>").append("" + ELNDictionary.Sample + " Hierarchy Graph for " + this.sample.identifier));
+		views.header.append($("<h1>").append("Hierarchy Graph: " + Util.getDisplayNameForEntity(this.sample)));
 		localInstance.hierarchyFilterController = new HierarchyFilterController(this.sample, function() { localInstance.filterSampleAndUpdate(); });
 		localInstance.hierarchyFilterController.init(views.header);
 
@@ -311,12 +311,7 @@ function SampleHierarchy(serverFacade, views, profile, sample) {
 							'style' : 'cursor:pointer; width:13px; height:18px;',
 						}));
 				
-				var nameLabel = null;
-				if(sample.properties[profile.propertyReplacingCode]) {
-					nameLabel = sample.code + "(" + sample.properties[profile.propertyReplacingCode] + ")";
-				} else {
-					nameLabel = sample.code;
-				}
+				var nameLabel = Util.getDisplayNameForEntity(sample);
 				
 				var $sampleLink = $('<a>', { 'href' : "javascript:mainController.changeView('showViewSamplePageFromPermId', '" + sample.permId + "')"}).text(nameLabel);
 				
@@ -326,7 +321,7 @@ function SampleHierarchy(serverFacade, views, profile, sample) {
 						.append($hideLink)
 						.append($dataLink)
 						.append($addChildLink)
-						.append(sample.sampleTypeCode + ':')
+						.append(' ' + Util.getDisplayNameFromCode(sample.sampleTypeCode) + ':')
 						.append($sampleLink);
 				
 				if(sample.showDataOnGraph) {
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/lib/grid/js/Grid.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/lib/grid/js/Grid.js
index 8857fff7a1553269711c611b4c2a87930bc9d054..aac6da56a854abc993c974ff5b5fd7bc2802a523 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/lib/grid/js/Grid.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/lib/grid/js/Grid.js
@@ -754,7 +754,7 @@ $.extend(Grid.prototype, {
 //				$(thisGrid.panel).find(".repeater").width(newWidth);
 
 				var headerHeight = $(thisGrid.panel).find(".repeater-header").outerHeight(true);
-				var listHeight = Math.max(100, $(thisGrid.panel).find(".repeater-list").outerHeight(true));
+				var listHeight = Math.max(144, $(thisGrid.panel).find(".repeater-list").outerHeight(true));
 				var footerHeight = $(thisGrid.panel).find(".repeater-footer").outerHeight(true);
 				var viewport = $(thisGrid.panel).find(".repeater-canvas")[0];
 				var scrollbarHeight = viewport.scrollWidth > viewport.offsetWidth ? thisGrid.scrollbarWidth : 0;
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/BBBServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/BBBServerFacade.js
new file mode 100644
index 0000000000000000000000000000000000000000..32c4989cf99266320443d5c48729e6440a8e559e
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/BBBServerFacade.js
@@ -0,0 +1,142 @@
+var BBBServerFacade = new function() {
+    this.getExperiment = function($container, model) {
+        require(["openbis",
+                 "as/dto/sample/fetchoptions/SampleFetchOptions",
+                 "as/dto/experiment/fetchoptions/ExperimentFetchOptions",
+                 "as/dto/experiment/id/ExperimentIdentifier",
+                 "as/dto/dataset/fetchoptions/DataSetFetchOptions"],
+        function(openbis, SampleFetchOptions,
+                          ExperimentFetchOptions,
+                          ExperimentIdentifier,
+                          DataSetFetchOptions) {
+
+            var v3 = new openbis(null);
+            v3._private.sessionToken = mainController.openbisV1._internal.sessionToken;
+
+            var webAppContext = v3.getWebAppContext();
+
+            var dataSetFetchOptions = new DataSetFetchOptions();
+            dataSetFetchOptions.withType();
+            dataSetFetchOptions.withPhysicalData();
+
+            var experimentId = new ExperimentIdentifier(model.experiment.identifier);
+            var fetchOptions = new ExperimentFetchOptions();
+            fetchOptions.withDataSetsUsing(dataSetFetchOptions);
+            fetchOptions.withType();
+
+            var sampleFetchOptions = new SampleFetchOptions();
+            sampleFetchOptions.withType().withPropertyAssignments().withPropertyType();
+            sampleFetchOptions.withProperties();
+            fetchOptions.withSamplesUsing(sampleFetchOptions);
+            v3.getExperiments([experimentId], fetchOptions).done(function(result) {
+                var experiment = result[model.experiment.identifier];
+                if (experiment.type.code == "BBB") {
+                    SnakemakeTrigger.paintTriggerContainers($container, model, experiment);
+                }
+            });
+        });
+    }
+
+    this.getExperiments = function($content) {
+        require(["openbis", "as/dto/sample/fetchoptions/SampleFetchOptions",
+                            "as/dto/experiment/fetchoptions/ExperimentFetchOptions",
+                            "as/dto/experiment/id/ExperimentIdentifier",
+                            "as/dto/dataset/fetchoptions/DataSetFetchOptions",
+                            "as/dto/experiment/search/ExperimentSearchCriteria",
+                            "as/dto/project/fetchoptions/ProjectFetchOptions"],
+        function(openbis, SampleFetchOptions,
+                          ExperimentFetchOptions,
+                          ExperimentIdentifier,
+                          DataSetFetchOptions,
+                          ExperimentSearchCriteria,
+                          ProjectFetchOptions) {
+
+            var v3 = new openbis();
+
+            v3.loginAsAnonymousUser().done(function(sessionToken) {
+                v3._private.sessionToken = sessionToken;
+                var webAppContext = v3.getWebAppContext();
+
+                var fetchOptions = new ExperimentFetchOptions();
+                fetchOptions.withType();
+                fetchOptions.withProperties();
+                fetchOptions.withRegistrator();
+
+                var projectFetchOptions = new ProjectFetchOptions();
+                projectFetchOptions.withSpace();
+
+                fetchOptions.withProjectUsing(projectFetchOptions);
+
+                var searchCriteria = new ExperimentSearchCriteria();
+                searchCriteria.withType();
+
+                v3.searchExperiments(searchCriteria, fetchOptions).done(function(result) {
+                    BBBServerFacade.getRoleAssignment($content, result.objects);
+                });
+            }).fail(function(result) {
+                console.log("Call failed to server: " + JSON.stringify(result));
+            });
+        });
+    }
+
+    this.getRoleAssignment = function ($content, experiments) {
+        require(["openbis", "as/dto/roleassignment/fetchoptions/RoleAssignmentFetchOptions",
+                            "as/dto/roleassignment/search/RoleAssignmentSearchCriteria"],
+        function(openbis, RoleAssignmentFetchOptions, RoleAssignmentSearchCriteria) {
+
+            var v3 = new openbis();
+
+            v3.loginAsAnonymousUser().done(function(sessionToken) {
+                v3._private.sessionToken = sessionToken;
+                var webAppContext = v3.getWebAppContext();
+
+                var criteria = new RoleAssignmentSearchCriteria();
+                criteria.withOrOperator();
+                criteria.withProject();
+                criteria.withSpace();
+
+                var fetchOptions = new RoleAssignmentFetchOptions();
+                fetchOptions.withSpace();
+                fetchOptions.withProject();
+                fetchOptions.withUser();
+                fetchOptions.withAuthorizationGroup();
+
+                v3.searchRoleAssignments(criteria, fetchOptions).done(function(result) {
+                    SnakemakeTable.paintTable($content, experiments, result.objects);
+                });
+            }).fail(function(result) {
+                console.log("Call failed to server: " + JSON.stringify(result));
+            });
+        });
+    }
+
+    this.callCustomASService = function(params, $model, action) {
+        var result_div = $('#callback_message');
+
+        require(["openbis",
+                 "as/dto/service/id/CustomASServiceCode",
+                 "as/dto/service/CustomASServiceExecutionOptions",
+                 "as/dto/experiment/id/ExperimentIdentifier"],
+        function(openbis, CustomASServiceCode, CustomASServiceExecutionOptions, ExperimentIdentifier) {
+
+            var v3 = new openbis(null);
+            v3._private.sessionToken = mainController.openbisV1._internal.sessionToken;
+            var webAppContext = mainController.openbisV3.getWebAppContext();
+            webAppContext['entityIdentifier'] = $model.experiment.identifier;
+
+            var serviceCode = new CustomASServiceCode("snakemake_service");
+            var options = new CustomASServiceExecutionOptions();
+
+            options.withParameter("webapp_context", webAppContext)
+            Object.keys(params).forEach(function(key) {
+                options.withParameter(key, params[key]);
+            });
+
+            v3.executeCustomASService(serviceCode, options).done(function(result) {
+                result_json = JSON.parse(result);
+                action(result_json)
+            });
+        });
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/UiComponents.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/UiComponents.js
new file mode 100644
index 0000000000000000000000000000000000000000..36e1f12a6404c1896e609c4d8c3292b65b86335e
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/UiComponents.js
@@ -0,0 +1,104 @@
+var UiComponents = new function() {
+
+    this.addDropdown = function(label, options, $container, selectAction) {
+        var $dropdown = UiComponents.getDropdown(options);
+        $inputGroup = UiComponents.getInputGroup($dropdown, label);
+        $inputGroup.css("margin-top", "3px");
+        $container.append($inputGroup);
+        $dropdown.select2({ width : "100%", theme : "bootstrap", minimumResultsForSearch: 10 });
+        if (selectAction) {
+           $dropdown.on('select2:select', function (e) {
+               selectAction(e.params.data.id);
+           });
+        }
+        $inputGroup.getValue = function() {
+           return $dropdown.val();
+        }
+        return $inputGroup;
+    }
+
+    this.getDropdown = function(options, value) {
+        var $input = $("<select>", { class : "form-control", type : "text" });
+        $input.append($("<option>", { "disabled" : true, "selected" : true, "value" : true }).text(" -- select an option -- "));
+        for (option of options) {
+            var $option = $("<option>");
+            $option.text(option.label);
+            $option.attr("value", option.value);
+            $input.append($option);
+        }
+        if (value) {
+            $input.val(value);
+        }
+        return $input;
+    }
+
+    this.getInputGroup = function($input, label) {
+        var $inputGroup = $("<div>", { class : "input-group" });
+        $inputGroup.append($("<span>", { class : "input-group-addon" }).text(label).css({ 'min-width' : '150px' }));
+        $inputGroup.append($input);
+        return $inputGroup;
+    }
+
+    this.getButton = function($html, action, size, icon) {
+        var buttonSize = size ? size : 'md';
+        var $button = $('<button>', {
+            type : 'button',
+            class : 'btn btn-default btn-' + buttonSize,
+            'aria-label' : 'Left Align'
+        });
+        if(icon) {
+            var $icon = $('<span>', { class : 'glyphicon ' + icon });
+            $icon.css("margin-right", "0.5rem");
+            $button.append($icon);
+        }
+        if($html) {
+            $button.append($("<span>").text($html));
+        }
+        if(action) {
+            $button.click(action);
+        }
+        return $button;
+    }
+
+    this.getFieldset = function(legendText) {
+        var $fieldset = $('<fieldset>');
+        if (legendText) {
+            var $legend = $('<legend>', { class : 'section-legend' }).append(legendText)
+            $fieldset.append($legend);
+        }
+        return $fieldset;
+    }
+
+    this.getLoader = function() {
+        return $('<div>').attr('class', 'loader col-centered');
+    }
+
+    // blocks given $component or whole screen of none given
+    this.startLoading = function($component) {
+        var params = {
+            message : this.getLoader(),
+            css: {
+                border: 'none',
+                padding: '15px',
+                backgroundColor: 'transparent',
+            },
+            overlayCSS: {
+                opacity : 0.1,
+            }
+        };
+        if ($component) {
+            $component.block(params);
+        } else {
+            $.blockUI(params);
+        }
+    }
+
+    // unblocks given $component or whole screen of none given
+    this.stopLoading = function($component) {
+        if ($component) {
+            $component.unblock();
+        } else {
+            $.unblockUI();
+        }
+    }
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/plugin.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/plugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..b6803e35362dff0ecd780bb4c5a171bc4d21425d
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/plugin.js
@@ -0,0 +1,37 @@
+function BBBHubTechnology() {
+    this.init();
+}
+
+$.extend(BBBHubTechnology.prototype, ELNLIMSPlugin.prototype, {
+    // This code is copy of openBis plugin. It was taken from here:
+    // https://sissource.ethz.ch/sis/bbb-hub/tree/master/openbis/core-plugins/bbb-hub/1/as/webapps/snakemake/html/js
+    // Now it is a ELN plugin.
+
+	init: function() {
+        loadJSResorce("./plugins/bbb-hub/UiComponents.js");
+        loadJSResorce("./plugins/bbb-hub/BBBServerFacade.js");
+        loadJSResorce("./plugins/bbb-hub/snakemake-table.js");
+        loadJSResorce("./plugins/bbb-hub/snakemake-trigger.js");
+	},
+
+	experimentFormTop : function($container, model) {
+	    BBBServerFacade.getExperiment($container, model);
+    },
+
+    experimentFormBottom : function($container, model) {
+    },
+
+    getExtraUtilities : function() {
+        return [{
+            icon : "fa fa-table",
+            uniqueViewName : "BBB_VIEW_NAME_TEST",
+            label : "Snakemake",
+            paintView : function($header, $content) {
+                    $header.append($("<h1>").append("Public Index Page"));
+                    BBBServerFacade.getExperiments($content);
+                }
+            }];
+    }
+});
+
+profile.plugins.push(new BBBHubTechnology());
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-table.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-table.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6add5714f1bd889e0379b02921fe668b826fe0a
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-table.js
@@ -0,0 +1,169 @@
+var SnakemakeTable = new function() {
+
+    this.paintTable = function(container, experiments, roles) {
+        var columns = [];
+
+        columns.push({
+            label : 'Code',
+            property : 'code',
+            isExportable: false,
+            sortable : true
+        });
+
+        columns.push({
+            label : 'Description',
+            property : 'description',
+            isExportable: false,
+            sortable : true
+        });
+
+        columns.push({
+            label : 'Group',
+            property : 'group',
+            isExportable: false,
+            sortable : true
+        });
+
+        columns.push({
+            label : 'E-mail',
+            property : 'email',
+            isExportable: false,
+            sortable : true
+        });
+
+        columns.push({
+            label : 'Special access',
+            property : 'specialAccess',
+            isExportable: false,
+            sortable : true
+        });
+
+        var getDataList = function(callback) {
+            var entities = [];
+
+            var rolesMap = SnakemakeTable.rolesMap(roles);
+
+            var dataList = [];
+
+            for (i = 0; i < experiments.length; i++) {
+                if (experiments[i].type === undefined || experiments[i].type.code !== "BBB") {
+                    continue;
+                }
+
+                var desc = experiments[i].properties["BBB.DESCRIPTION"];
+                var group = experiments[i].properties["BBB.GROUP"];
+                var registrator = experiments[i].registrator;
+                var projectCode = experiments[i].project.code;
+                var spaceCode = experiments[i].project.space.code;
+
+                var model = {		'code' : experiments[i].code,
+                                    'description' : desc === undefined ? "" : desc,
+                                    'group' : group === undefined ? "" : group,
+                                    'email' : registrator === null ? "" : registrator.email,
+                                    'specialAccess' : SnakemakeTable.getSpecialAccess(rolesMap, projectCode, spaceCode)
+                };
+
+                dataList.push(model);
+            }
+            callback(dataList);
+        };
+
+        var dataGridController = new DataGridController(null, columns, [], null, getDataList, null, true, "ENTITY_TABLE_BBB", null, 90);
+        dataGridController.init(container);
+    }
+
+    this.prepareData = function(experiments, roles) {
+        var data = [];
+
+        var rolesMap = SnakemakeTable.rolesMap(roles);
+
+        for (i = 0; i < experiments.length; i++) {
+            var row = [];
+
+            if (experiments[i].type === undefined || experiments[i].type.code !== "BBB") {
+                continue;
+            }
+
+            var desc = experiments[i].properties["BBB.DESCRIPTION"];
+            var group = experiments[i].properties["BBB.GROUP"];
+            var registrator = experiments[i].registrator;
+            var projectCode = experiments[i].project.code;
+            var spaceCode = experiments[i].project.space.code;
+
+            row.push(experiments[i].code);
+            row.push(desc === undefined ? "" : desc);
+            row.push(group === undefined ? "" : group);
+            row.push(registrator === null ? "" : registrator.email);
+            row.push(SnakemakeTable.getSpecialAccess(rolesMap, projectCode, spaceCode));
+
+            data.push(row);
+        }
+
+        return data;
+    }
+
+    this.getSpecialAccess = function(rolesMap, projectCode, spaceCode) {
+        var projectUsers = rolesMap["projects"][projectCode] === undefined ? "" : rolesMap["projects"][projectCode]["users"].join(", ");
+        var projectGroups = rolesMap["projects"][projectCode] === undefined ? "" : rolesMap["projects"][projectCode]["groups"].join(", ");
+
+        var spaceUsers = rolesMap["space"][spaceCode] === undefined ? "" : rolesMap["space"][spaceCode]["users"].join(", ");
+        var spaceGroups = rolesMap["space"][spaceCode] === undefined ? "" : rolesMap["space"][spaceCode]["groups"].join(", ");
+
+        var users = projectUsers + (projectUsers !== "" && spaceUsers !== "" ? ", " : "") + spaceUsers;
+        var groups = projectGroups + (projectGroups !== "" && spaceGroups !== ""  ? ", " : "") + spaceGroups;
+
+        var access = "";
+        if (users !== "") {
+            access += "<span style='font-weight:bold'>Users: </span>" + users;
+        }
+
+        if (groups !== "") {
+            if (access !== "") {
+                access += "<br/>";
+            }
+            access += "<span style='font-weight:bold'>Groups: </span>" + groups;
+        }
+
+        return access;
+    }
+
+    this.rolesMap = function(roles) {
+        var rolesMap = new Map();
+
+        var projects = new Map();
+        var spaces = new Map();
+
+        rolesMap["projects"] = projects;
+        rolesMap["space"] = spaces;
+
+        for (i = 0; i < roles.length; i++) {
+            var user = roles[i].user == null ? null : roles[i].user.userId;
+            var group = roles[i].authorizationGroup == null ? null : roles[i].authorizationGroup.code;
+
+            if (roles[i].project !== null) {
+                var value = projects[roles[i].project.code];
+                projects[roles[i].project.code] = SnakemakeTable.appendValue(value, user, group);
+            } else {
+                var value = spaces[roles[i].space.code];
+                spaces[roles[i].space.code] = SnakemakeTable.appendValue(value, user, group);
+            }
+        }
+
+        return rolesMap;
+    }
+
+    this.appendValue = function(value, user, group) {
+        if (value === undefined) {
+            value = new Map();
+            value["users"] = new Array();
+            value["groups"] = new Array();
+        }
+
+        if (user !== null) {
+            value["users"].push(user);
+        } else {
+            value["groups"].push(group);
+        }
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-trigger.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-trigger.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3756fdfc4a20c0d1432b9973209524ad2dbe27b
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/bbb-hub/snakemake-trigger.js
@@ -0,0 +1,161 @@
+var SnakemakeTrigger = new function() {
+
+    var groups;
+    var propTypeCodeToLabelMap = {};
+    var dataSetPaths;
+    var $formContainerAll;
+    var $formContainerGroups;
+    var $dropdownGroupBy;
+    var $dropdownGroup1;
+    var $dropdownGroup2;
+    var $button;
+    var $model;
+
+    this.paintTriggerContainers = function($container, model, experiment) {
+        $model = model;
+        SnakemakeTrigger.paintFormContainers($container);
+        SnakemakeTrigger.paintProcessAll();
+        SnakemakeTrigger.paintGroupBy(experiment);
+        SnakemakeTrigger.paintGroupValues();
+        groups = SnakemakeTrigger.getGroups(experiment);
+        files = SnakemakeTrigger.getFiles(experiment);
+    }
+
+    this.paintFormContainers = function($container) {
+        var $fieldsetAll = UiComponents.getFieldset("Process all comparisons");
+        var $fieldsetGroups = UiComponents.getFieldset("Pick groups to compare");
+        $formContainerAll = $('<div>').addClass('form-group');
+        $formContainerGroups = $('<div>').addClass('form-group');
+        $fieldsetAll.append($formContainerAll);
+        $fieldsetGroups.append($formContainerGroups);
+        $container.append($fieldsetAll).append($fieldsetGroups);
+    }
+
+    this.paintProcessAll = function() {
+        var $buttonAll = UiComponents.getButton("Start processing", SnakemakeTrigger.startProcessingAll);
+        $formContainerAll.append($buttonAll);
+    }
+
+    this.paintGroupBy = function(experiment) {
+        var groupByOptions = [];
+        if (experiment.samples.length > 0) {
+            var sample = experiment.samples[0];
+            for (var i = 0; i < sample.type.propertyAssignments.length; i++) {
+                var propertyAssignment = sample.type.propertyAssignments[i];
+                groupByOptions.push({
+                    label: propertyAssignment.propertyType.label,
+                    value: propertyAssignment.propertyType.code,
+                });
+                propTypeCodeToLabelMap[propertyAssignment.propertyType.code] = propertyAssignment.propertyType.label;
+            }
+        }
+
+        $dropdownGroupBy = UiComponents.addDropdown("Group by", groupByOptions, $formContainerGroups, SnakemakeTrigger.paintGroupValues);
+    }
+
+    this.paintGroupValues = function(propertyType) {
+        if (propertyType) {
+            var groupOptions = groups[propertyType].map(function(value) { return {
+                label: value,
+                value: value,
+            }});
+        } else {
+            var groupOptions = [];
+        }
+
+        if ($dropdownGroup1) {
+            $dropdownGroup1.remove();
+        }
+        if ($dropdownGroup2) {
+            $dropdownGroup2.remove();
+        }
+        if ($button) {
+            $button.remove();
+        }
+
+        $dropdownGroup1 = UiComponents.addDropdown("Group 1", groupOptions, $formContainerGroups);
+        $dropdownGroup2 = UiComponents.addDropdown("Group 2", groupOptions, $formContainerGroups);
+
+        $button = UiComponents.getButton("Start processing", SnakemakeTrigger.startProcessing);
+        $button.css("margin-top", "20px");
+        $formContainerGroups.append($button);
+    }
+
+    this.getGroups = function(experiment) {
+        var groups = {};
+        for (var i = 0; i < experiment.samples.length; i++) {
+            var sample = experiment.samples[i];
+            // collect groups
+            for (var propertyKey in sample.properties) {
+                if (sample.properties.hasOwnProperty(propertyKey)) {
+                    var propertyValue = sample.properties[propertyKey];
+                    if (groups.hasOwnProperty(propertyKey) == false) {
+                        groups[propertyKey] = [];
+                    }
+                    if (groups[propertyKey].indexOf(propertyValue) == -1) {
+                        groups[propertyKey].push(propertyValue);
+                    }
+                }
+            }
+        }
+        return groups;
+    }
+
+    this.getFiles = function(experiment) {
+        dataSetPaths = [];
+        for (var i = 0; i < experiment.dataSets.length; i++) {
+            var dataSet = experiment.dataSets[i];
+            if (dataSet.type.code == "FASTQ" || dataSet.type.code == "METADATA") {
+                var path = dataSet.physicalData.shareId + "/" + dataSet.physicalData.location;
+                dataSetPaths.push(path);
+            }
+        }
+    }
+
+    this.startProcessingAll = function() {
+        UiComponents.startLoading();
+        var jsonrpc = {
+            "method": "start_processing_all",
+            "params": {
+                "data_set_paths": dataSetPaths
+            }
+        };
+        BBBServerFacade.callCustomASService(jsonrpc, $model, function(result) {
+            UiComponents.stopLoading();
+            if (result.success) {
+              alert("Processing started.");
+            } else {
+              alert("Error: " + result.data);
+            }
+        });
+    }
+
+    this.startProcessing = function() {
+        var groupSelection = {
+            groupBy: propTypeCodeToLabelMap[$dropdownGroupBy.getValue()],
+            group1: $dropdownGroup1.getValue(),
+            group2: $dropdownGroup2.getValue(),
+        }
+        if (!groupSelection.groupBy || !groupSelection.group1 || !groupSelection.group2) {
+            alert("Select both groups first.");
+            return;
+        }
+        UiComponents.startLoading();
+
+        var jsonrpc = {
+            "method": "start_processing",
+            "params": {
+                "group_selection": groupSelection,
+                "data_set_paths": dataSetPaths
+            }
+        };
+        BBBServerFacade.callCustomASService(jsonrpc, $model, function(result) {
+            UiComponents.stopLoading();
+            if (result.success) {
+                alert("Processing started.");
+            } else {
+                alert("Error: " + result.data);
+            }
+        });
+    }
+}
\ No newline at end of file