diff --git a/jupyter-openbis-extension/sample.py b/jupyter-openbis-extension/sample.py index 752dfe710c37eb4cf0af6fdf48d8b3fd63310f8a..3b0bf5468ff9a09a3ed0110388facb23c9fed977 100644 --- a/jupyter-openbis-extension/sample.py +++ b/jupyter-openbis-extension/sample.py @@ -5,8 +5,8 @@ from .connection import openbis_connections from urllib.parse import parse_qs - def get_entity_for_identifier(conn, identifier): + entity = None try: entity = conn.openbis.get_sample(identifier) except Exception as exc: diff --git a/jupyter-openbis-extension/static/downloadDialog.js b/jupyter-openbis-extension/static/downloadDialog.js index 304d4a7f75b6a608b9dee59f21bba066a4fb8863..b948043a7f2c414249b059b0513df21177ad0239 100644 --- a/jupyter-openbis-extension/static/downloadDialog.js +++ b/jupyter-openbis-extension/static/downloadDialog.js @@ -1,10 +1,11 @@ define([ "base/js/dialog", + "jquery", "./common", "./state", "./entitySearcher" ], - function (dialog, common, state, entitySearcher) { + function (dialog, $, common, state, entitySearcher) { var spinner = document.createElement("IMG") spinner.className="openbis-feedback" @@ -120,9 +121,9 @@ define([ return false } - currentEntityIdentifier = entityIdentifier.value + currentEntityIdentifier = entityIdentifier.firstChild.value if (!currentEntityIdentifier) { - alert('Please specify a Sample or Experiment identifier/permId') + alert('Please specify an Entity identifier/permId') return false } var url = env.notebook.base_url @@ -168,19 +169,11 @@ define([ var showDataSets = document.createElement("DIV") var title = document.createElement("STRONG") - title.textContent = "Sample or Experiment identifier/permId: " + title.textContent = "Entity identifier/permId: " showDataSets.appendChild(title) showDataSets.style.marginTop = '10px' - // TODO Build and replace this component - // var entityIdentifier = entitySearcher.getEntitySearcher(state) - - var entityIdentifier = document.createElement("INPUT") - entityIdentifier.type = "text" - entityIdentifier.name = "entityIdentifier" - entityIdentifier.size = 40 - entityIdentifier.placeholder = "Sample or Experiment identifier/permId" - entityIdentifier.value = state.entityIdentifier + var entityIdentifier = entitySearcher.getEntitySearcherForDownload(state) var datasets_table = document.createElement("DIV") var pagingContainer = document.createElement("DIV") @@ -228,7 +221,6 @@ define([ download_dialog_box.appendChild(path) function saveState() { - state.entityIdentifier = entityIdentifier.value state.directPermId = datasetPermId.value state.workingDirectory = downloadPath.value } diff --git a/jupyter-openbis-extension/static/entitySearcher.js b/jupyter-openbis-extension/static/entitySearcher.js index 1b74fb9945a1896056574b23baf84b9e129f6554..889db406d2be343c36739cf6107c6ed26a4c455a 100644 --- a/jupyter-openbis-extension/static/entitySearcher.js +++ b/jupyter-openbis-extension/static/entitySearcher.js @@ -1,78 +1,240 @@ -define([], - function () { +define(["jquery", "./jquery-select2/js/select2.min"], + function($, select2) { return { - loadResorce(pathToResource, onLoad, jsOrCss) { - var resource = null; + loadResource(pathToResource, jsOrCss, onLoad) { + var resource = null - if(jsOrCss === 'js') { - resource = document.createElement('script'); - resource.type = 'text/javascript'; - resource.src = pathToResource; + if (jsOrCss === 'js') { + resource = document.createElement('script') + resource.type = 'text/javascript' + resource.src = pathToResource } - - if(jsOrCss === 'css') { - resource = document.createElement('link'); - resource.type = 'text/css'; - resource.rel = 'stylesheet'; - resource.href = pathToResource; + + if (jsOrCss === 'css') { + resource = document.createElement('link') + resource.type = 'text/css' + resource.rel = 'stylesheet' + resource.href = pathToResource } - resource.onload = onLoad; - resource.onreadystatechange= function () { - if (this.readyState == 'complete') { - onLoad(); - } + resource.onload = onLoad + resource.onreadystatechange = function() { + if (this.readyState == 'complete') { + onLoad() + } } - - var head = document.getElementsByTagName('head')[0]; - head.appendChild(resource); + + var head = document.getElementsByTagName('head')[0] + head.appendChild(resource) }, getRequireJSV3Config(baseURL) { - return { - baseUrl : baseURL + "/openbis/resources/api/v3", - paths : { - "stjs" : "lib/stjs/js/stjs", - "underscore" : "lib/underscore/js/underscore", - "moment" : "lib/moment/js/moment" - }, - shim : { - "stjs" : { - exports : "stjs", - deps : [ "underscore" ] - }, - "underscore" : { - exports : "_" - } - } - } - }, - getEntitySearcher(state) { - var _this = this; + return { + baseUrl: baseURL + "/openbis/resources/api/v3", + paths: { + "stjs": "lib/stjs/js/stjs", + "underscore": "lib/underscore/js/underscore", + "moment": "lib/moment/js/moment" + }, + shim: { + "stjs": { + exports: "stjs", + deps: ["underscore"] + }, + "underscore": { + exports: "_" + } + } + } + }, + getEntitySearcherForDownload(state) { + return this.getEntitySearcher(state, false) + }, + getEntitySearcherForUpload(state) { + return this.getEntitySearcher(state, true) + }, + getEntitySearcher(state, upload) { + var _this = this var connection_name = state.connection.name if (!connection_name) { alert('Please choose a connection') return false } - if(!state.openbisService) { - var config = this.getRequireJSV3Config(state.connection.dto.url) - require.config(config) - require(['openbis'], function(openbis) { - var apiUrl = state.connection.dto.url + "/openbis/openbis/rmi-application-server-v3.json" - var v3 = new openbis(apiUrl) - v3.login(state.connection.dto.username, state.connection.dto.password) - .done(function(sessionToken) { - state.openbisService = v3 - alert('openbis v3 service login succeed for ' + apiUrl +' : trusted-cross-origin-domains is set.') - }).fail(function(result) { - alert('openbis v3 service login failed for ' + apiUrl +' : trusted-cross-origin-domains is probably not set.') - }); - }); + var element = document.createElement("SPAN") + element.innerHTML = "<span style='color:orange;margin:5px'>loading...</span>" + if (!state.openbisService) { + require.config(this.getRequireJSV3Config(state.connection.dto.url)) + require(["openbis", "as/dto/experiment/search/ExperimentSearchCriteria", + "as/dto/experiment/fetchoptions/ExperimentFetchOptions", + "as/dto/sample/search/SampleSearchCriteria", + "as/dto/sample/fetchoptions/SampleFetchOptions"], function(openbis) { + var apiUrl = state.connection.dto.url + "/openbis/openbis/rmi-application-server-v3.json" + var v3 = new openbis(apiUrl) + v3.login(state.connection.dto.username, state.connection.dto.password) + .done(function(sessionToken) { + state.openbisService = v3 + _this.loadResource("/nbextensions/openbis/jquery-select2/css/select2.min.css", 'css', function() { + _this.createDropdown(element, state, upload) + }) + }).fail(function(result) { + alert('openbis v3 service login failed for ' + apiUrl + + " : property 'trusted-cross-origin-domains' is probably not set in service.properties.") + }) + }) + } else { + _this.createDropdown(element, state, upload) } - var element = document.createElement("SPAN") return element + }, + createDropdown(container, state, upload) { + var _this = this + var $select = $("<select>", {class : 'form-control'}) + $select.attr("multiple", "multiple") + $select.attr("required", "required") + + container.innerHTML = null + $select.each(function() { + container.appendChild(this) + }) + $select.select2({ + width: upload ? '100%' : '80%', + maximumSelectionLength: 1, + minimumInputLength: 2, + placeholder : "Entity identifier/permId", + ajax: { + delay: 1000, + processResults: function (data) { + var results = [] + + for(var dIdx = 0; dIdx < data.length; dIdx++) { + var group = { + text: data[dIdx].type, + children : [] + } + + var entities = data[dIdx].objects + for (var eIdx = 0; eIdx < entities.length; eIdx++) { + group.children.push({ + id : entities[eIdx].permId.permId, + text : _this.getDisplayName(entities[eIdx]), + data : { + id : entities[eIdx].permId.permId, + text : _this.getDisplayName(entities[eIdx]), + data : entities[eIdx] + } + }) + } + + if (entities.length > 0) { + results.push(group) + } + } + + return { + "results": results, + "pagination": { + "more": false + } + } + }, + transport: function (params, success, failure) { + var searchResults = [] + _this.searchExperiments(state, params, function(result) { + searchResults.push(result) + _this.searchSamples(state, params, function(result) { + searchResults.push(result) + success(searchResults) + }) + }) + return { + abort : function () { /* Not implemented */ } + } + } + } + }) + $select.on("select2:select", function() { + if (upload) { + state.uploadEntity = _this.getSelected($select)[0] + } else { + state.entity = _this.getSelected($select)[0] + } + }) + if (upload && state.uploadEntity) { + _this.addSelected($select, state.uploadEntity) + } else if (upload == false && state.entity) { + _this.addSelected($select, state.entity) + } + }, + searchExperiments(state, params, action) { + var ExperimentSearchCriteria = require("as/dto/experiment/search/ExperimentSearchCriteria") + var searchCriteria = new ExperimentSearchCriteria().withOrOperator() + searchCriteria.withCode().thatContains(params.data.q) + searchCriteria.withProperty("$NAME").thatContains(params.data.q) + var ExperimentFetchOptions = require("as/dto/experiment/fetchoptions/ExperimentFetchOptions") + var fetchOptions = new ExperimentFetchOptions() + fetchOptions.withProperties() + state.openbisService.searchExperiments(searchCriteria, fetchOptions).done(action) + }, + searchSamples(state, params, action) { + var SampleSearchCriteria = require("as/dto/sample/search/SampleSearchCriteria") + var searchCriteria = new SampleSearchCriteria().withOrOperator() + searchCriteria.withCode().thatContains(params.data.q) + searchCriteria.withProperty("$NAME").thatContains(params.data.q) + var SampleFetchOptions = require("as/dto/sample/fetchoptions/SampleFetchOptions") + var fetchOptions = new SampleFetchOptions() + fetchOptions.withProperties() + state.openbisService.searchSamples(searchCriteria, fetchOptions).done(action) + }, + getDisplayName(entity) { + var text = "" + var propertyReplacingCode = "$NAME" + if (entity.identifier && entity.identifier.identifier) { + text = entity.identifier.identifier + } + if (entity.properties && entity.properties[propertyReplacingCode]) { + text += " (" + entity.properties[propertyReplacingCode] + ")" + } + return text + }, + getSelected($select) { + var selected = $select.select2('data') + var entities = [] + for (var eIdx = 0; eIdx < selected.length; eIdx++) { + if (selected[eIdx].data) { + entities.push(selected[eIdx].data.data) + } + if (selected[eIdx].element.data) { + entities.push(selected[eIdx].element.data.data) + } + } + return entities + }, + addSelected($select, v3entity) { + var text = this.getDisplayName(v3entity) + var id = null + if (v3entity.permId && v3entity.permId.permId) { //Only v3 objects supported + id = v3entity.permId.permId + } else { + throw { + name: "NonV3ObjectException", + message: "Object without v3 permId", + toString: function() { + return this.name + ": " + this.message + } + } + } + + var data = { + id : id, + text : text, + data : v3entity + } + + var newOption = new Option(text, id, true, true) + newOption.data = data + $select.append(newOption).trigger('change') } } } -) \ No newline at end of file +) diff --git a/jupyter-openbis-extension/static/spinner.gif b/jupyter-openbis-extension/static/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..58d18d4c3a5baac5a486e28b639f050baceffb3e Binary files /dev/null and b/jupyter-openbis-extension/static/spinner.gif differ diff --git a/jupyter-openbis-extension/static/state.js b/jupyter-openbis-extension/static/state.js index 2a0f35c8254ced361b30f7c9f122c8affb5b41f0..71ea8e23d5a13982ed94c120171ef2e9674048d1 100644 --- a/jupyter-openbis-extension/static/state.js +++ b/jupyter-openbis-extension/static/state.js @@ -12,7 +12,7 @@ define([], // upload dialog uploadDataSetType: null, uploadDataSetTypes: {}, - uploadEntityIdentifier: '', + uploadEntity: null, datasetCheckboxes: [], fileCheckboxes: [], selectedFiles: [], @@ -20,11 +20,11 @@ define([], // download dialog selectedDatasets: new Set([]), - entityIdentifier: '', + entity: null, workingDirectory: '', // openBIS v3 connection - openBISV3 : null + openbisService : null } } ) \ No newline at end of file diff --git a/jupyter-openbis-extension/static/uploadDialog.js b/jupyter-openbis-extension/static/uploadDialog.js index 0690d40cf1d5bd58e64824a8eef1ce93fa945b08..9f848ee0a4d2d4dc6a0d8a122a1a2749803aeb06 100644 --- a/jupyter-openbis-extension/static/uploadDialog.js +++ b/jupyter-openbis-extension/static/uploadDialog.js @@ -4,9 +4,9 @@ define([ "jquery", "./state", "./common", + "./entitySearcher" ], - function (dialog, utils, $, state, common) { - + function (dialog, utils, $, state, common, entitySearcher) { var errorElements = { } function createErrorElement(name) { var element = document.createElement("STRONG") @@ -23,10 +23,10 @@ define([ var spinner = document.createElement("IMG") spinner.className="openbis-feedback" spinner.src="" - function showSpinner() { - spinner.src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.5.8/ajax-loader.gif" + function showSpinner(env) { + spinner.src = env.notebook.base_url + "nbextensions/openbis/spinner.gif" } - function hideSpinner() { + function hideSpinner(env) { spinner.src="" } @@ -68,7 +68,7 @@ define([ response.json() .then(function (data) { var change_input_fields = function () { - hideSpinner() + hideSpinner(env) cleanErrors() var oldType = state.uploadDataSetType @@ -203,22 +203,16 @@ define([ getDatasetTypes(env, state.connection.name, dataset_types, input_fields) var sample_title = document.createElement("STRONG") - sample_title.textContent = "Sample or Experiment identifier/permId" + sample_title.textContent = "Entity" var sample_error = createErrorElement('entityIdentifier') - var entityIdentifier = document.createElement("INPUT") - entityIdentifier.type = "text" - entityIdentifier.name = 'entityIdentifier' - entityIdentifier.placeholder = "Sample or Experiment identifier/permId" - entityIdentifier.value = state.uploadEntityIdentifier - entityIdentifier.size = "90" - entityIdentifier.style.width="100%" + var entityIdentifier = entitySearcher.getEntitySearcherForUpload(state) var ds_title = document.createElement("STRONG") var dataSetListContainer = document.createElement("DIV") if (env.notebook.metadata.datasets) { - ds_title.textContent = "DataSets" + ds_title.textContent = "Parent DataSets" dataSetListContainer.style.maxHeight="150px" dataSetListContainer.style.overflow="auto" get_dataset_list(env, dataSetListContainer) @@ -256,7 +250,6 @@ define([ state.uploadDataSetTypes[state.uploadDataSetType][element.name] = element.value } } - state.uploadEntityIdentifier = entityIdentifier.value state.unselectedDatasets = state.datasetCheckboxes.filter(cb => !cb.checked).map(cb => cb.value) state.selectedFiles = state.fileCheckboxes.filter(cb => cb.checked).map(cb => cb.value) } @@ -286,7 +279,7 @@ define([ "type": dataset_types.value, "files": files, "parents": state.datasetCheckboxes.filter(cb => cb.checked).map(cb => cb.value), - "entityIdentifier": entityIdentifier.value, + "entityIdentifier": entityIdentifier.firstChild.value, "props": props } @@ -333,7 +326,7 @@ define([ } } - showSpinner() + showSpinner(env) cleanErrors() utils.ajax(settings) return false