diff --git a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java index bb2e981e421ad6d5595c932742f3fb5298941207..a638dcbb7ebe6025f1fdf67e530747b5c47a7563 100644 --- a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java +++ b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/tracking/main/TrackingBO.java @@ -17,18 +17,10 @@ package ch.ethz.bsse.cisd.dsu.tracking.main; import java.io.File; +import java.io.IOException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; import ch.ethz.bsse.cisd.dsu.tracking.dto.TrackedEntities; import ch.ethz.bsse.cisd.dsu.tracking.dto.TrackingStateDTO; @@ -441,10 +433,12 @@ public class TrackingBO if (commandLineMap.containsKey(TrackingClient.CL_PARAMETER_COPY_DATA_SETS)) { // Data Sets with higher priority get transferred first - extraDataSetCopy(params, toTransferDataSetsHighPriority); - extraDataSetCopy(params, toTransferDataSets); + // extraSCICOREDataSetListCopy(params, toTransferDataSetsHighPriority); + // extraSCICOREDataSetListCopy(params, toTransferDataSets); } + extraSCICOREDataSetListCopy(params, filteredDataSets); + LogUtils.info("Found " + filteredDataSets.size() + " data sets which are connected to samples in " + filterList.toString()); setLaneProperties(changedTrackingMap, v3, v3SessionToken); @@ -486,11 +480,100 @@ public class TrackingBO return new SimpleDateFormat(DATE_FORMAT_PATTERN).format(Calendar.getInstance().getTime()); } - - + private static final SimpleDateFormat LIST_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSSS"); + + private static void extraSCICOREDataSetListCopy(Parameters params, List<AbstractExternalData> dataSets) { + LogUtils.info("SCICORE dataset listing - Start"); + String datasetListFileBytes = ""; + for (AbstractExternalData dataSet:dataSets) { + datasetListFileBytes += dataSet.getPermId() + "\n"; + } + LogUtils.info("SCICORE dataset listing - Content : " + datasetListFileBytes); + + String timestamp = LIST_TIMESTAMP_FORMAT.format(new Date()); + String tempCanonicalPath = null; + try { + tempCanonicalPath = java.nio.file.Files.createTempDirectory(timestamp + "-tracking-temp-").toFile().getCanonicalPath(); + } catch (Exception e) { + throw new RuntimeException(e); + } + LogUtils.info("SCICORE dataset listing - temp : " + tempCanonicalPath); + + String datasetTypeCode = "FASTQ_GZ"; + String datasetName = timestamp + "_LIST"; + File datasetSource = new File(tempCanonicalPath + "/" + datasetName); + datasetSource.mkdirs(); + LogUtils.info("SCICORE dataset listing - datasetSource : " + datasetSource.getPath()); + + File datasetListFile = new File(tempCanonicalPath + "/" + datasetName + "/" + timestamp + ".tsv"); + try { + java.nio.file.Files.write(datasetListFile.toPath(), datasetListFileBytes.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + LogUtils.info("SCICORE dataset listing - writing to : " + datasetListFile.getPath()); + + File datasetDestination = new File(params.getDestinationFolderMap().get(datasetTypeCode), datasetName); + datasetDestination.mkdirs(); + + LogUtils.info("SCICORE dataset listing - datasetDestination : " + datasetDestination.getPath()); + + RsyncCopier copier = null; + File rsyncBinary = new File(params.getRsyncBinary()); + if (params.getRsyncFlags() != null) + { + LogUtils.info("SCICORE dataset listing - RSYNC WITH EXTRA PARAMETERS "); + List<String> cmdLineOptions = new ArrayList<String>(params.getRsyncFlags().length); + Collections.addAll(cmdLineOptions, params.getRsyncFlags()); + copier = new RsyncCopier(rsyncBinary, null, cmdLineOptions.toArray(new String[cmdLineOptions.size()])); + } else + { + LogUtils.info("SCICORE dataset listing - RSYNC NO EXTRA PARAMETERS "); + copier = new RsyncCopier(rsyncBinary, (File) null, ""); + } + + final long start = System.currentTimeMillis(); + LogUtils.info("SCICORE dataset listing - BEFORE RSYNC"); + Status status = copier.copyContent(datasetSource, datasetDestination, null, null); + final long end = System.currentTimeMillis(); + LogUtils.info("SCICORE dataset listing - AFTER RSYNC TIME: " + (end-start) + " millis."); + LogUtils.info("SCICORE dataset listing - AFTER RSYNC STATUS: " + status.toString()); + + if (status.isError()) + { + String exceptionMsg = + (status == null) ? "" : " Unexpected exception has occured: " + + status.toString(); + + List<EMailAddress> adminEmails = new ArrayList<EMailAddress>(); + for (String adminEmail : params.getAdminEmail().split(",")) + { + adminEmails.add(new EMailAddress(adminEmail.trim())); + } + + EnvironmentFailureException ret = + LogUtils.environmentError( + "Data transfer failed for %s. %s", + datasetName, exceptionMsg); + + IMailClient emailClient = params.getMailClient(); + emailClient.sendEmailMessage("GFB Tracker: Data transfer problem", + ret.getLocalizedMessage(), null, + new EMailAddress(params.getNotificationEmail()), + adminEmails.toArray(new EMailAddress[0])); + } else { + File datasetDestinationMarkerFile = new File(params.getDestinationFolderMap().get(datasetTypeCode), ".MARKER_is_finished_" + datasetName); + try { + datasetDestinationMarkerFile.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + private static void extraDataSetCopy(Parameters params, List<AbstractExternalData> dataSets) { - RsyncCopier copier = null; File rsyncBinary = new File(params.getRsyncBinary()); String base_path_string = params.getDssRoot(); diff --git a/installation/resource/installer/bin/bisup.sh b/installation/resource/installer/bin/bisup.sh index 4d86fda34dd19a2a2b506698655bf41ed5b79f14..ac4aea8999c6458804d7750c0f459b0fdd8c3d5e 100755 --- a/installation/resource/installer/bin/bisup.sh +++ b/installation/resource/installer/bin/bisup.sh @@ -27,6 +27,14 @@ STARTING_MESSAGE="STARTING SERVER" STARTED_MESSAGE="SERVER STARTED" ERROR_MESSAGE="ERROR" +function assertNoError() { + error=`egrep "($STARTING_MESSAGE|$ERROR_MESSAGE)" $1 | egrep -A 1 "$STARTING_MESSAGE" | tail -1 | grep "$ERROR_MESSAGE"` + if [ -n "$error" ]; then + echo "Failed: $error" + exit 1; + fi +} + if [ -n "$(readlink $0)" ]; then # handle symbolic links scriptName=$(readlink $0) @@ -46,10 +54,12 @@ fi JETTY_HOME=$BASE/../servers/openBIS-server/jetty OPENBIS_LOG=$JETTY_HOME/logs/openbis_log.txt JETTY_LOG=$JETTY_HOME/logs/jetty.out +STARTED_MARKER=$JETTY_HOME/SERVER_STARTED TIMEOUT=120 echo Starting openBIS... echo $STARTING_MESSAGE >> $OPENBIS_LOG +rm -f $STARTED_MARKER # This variable suits as a workaround for cases where newly created files # have non-zero age according to our age measuring function @@ -69,30 +79,13 @@ while [ "$bisLogAgeInSeconds" -lt $TIMEOUT ] || [ "$jettyLogAgeInSeconds" -lt $T echo -n "." sleep 2 - # get the message "STARTING_SERVER" + the next matching message (i.e. STARTED or ERROR) - bisLogStatus=`egrep "($STARTING_MESSAGE|$STARTED_MESSAGE|$ERROR_MESSAGE)" $OPENBIS_LOG | egrep -A 1 "$STARTING_MESSAGE" | tail -1` - jettyLogStatus=`egrep "($STARTING_MESSAGE|$STARTED_MESSAGE|$ERROR_MESSAGE)" $JETTY_LOG | egrep -A 1 "$STARTING_MESSAGE" | tail -1` - - started=`echo $bisLogStatus | grep "$STARTED_MESSAGE"` - if [ -n "$started" ]; then - started=`echo $jettyLogStatus | grep "$STARTED_MESSAGE"` - if [ -n "$started" ]; then - echo "Done." - exit 0; - fi - fi - - error=`echo $bisLogStatus | grep "$ERROR_MESSAGE"` - if [ -n "$error" ]; then - echo "Failed: $error" - exit 1; - fi - - error=`echo $jettyLogStatus | grep "$ERROR_MESSAGE"` - if [ -n "$error" ]; then - echo "Failed: $error" - exit 1; + if [ -f $STARTED_MARKER ]; then + echo "Done." + exit 0; fi + + assertNoError $OPENBIS_LOG + assertNoError $JETTY_LOG fileAgeInSeconds $OPENBIS_LOG bisLogAgeInSeconds=$(expr $? - $ageOfNewFile) diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js index 56bf7882d6194095a63ea6f39ba9a5a7d22bcdbd..ec8bc0a33c2290a23905b7365e4db615ed47dd7c 100644 --- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js +++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js @@ -173,6 +173,8 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, defaultOp this.ReportingServiceSearchCriteria = dtos.ReportingServiceSearchCriteria; this.ReportingServiceFetchOptions = dtos.ReportingServiceFetchOptions; this.ReportingServiceExecutionOptions = dtos.ReportingServiceExecutionOptions; + this.Rights = dtos.Rights; + this.RightsFetchOptions = dtos.RightsFetchOptions; this.ProcessingServiceSearchCriteria = dtos.ProcessingServiceSearchCriteria; this.ProcessingServiceFetchOptions = dtos.ProcessingServiceFetchOptions; this.ProcessingServiceExecutionOptions = dtos.ProcessingServiceExecutionOptions; @@ -221,6 +223,7 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, defaultOp this.GetTagsOperation = dtos.GetTagsOperation; this.GetAuthorizationGroupsOperation = dtos.GetAuthorizationGroupsOperation; this.GetRoleAssignmentsOperation = dtos.GetRoleAssignmentsOperation; + this.GetRightsOperation = dtos.GetRightsOperation; this.GetPersonsOperation = dtos.GetPersonsOperation; this.GetExternalDmsOperation = dtos.GetExternalDmsOperation; this.GetSemanticAnnotationsOperation = dtos.GetSemanticAnnotationsOperation; diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js index 5f3f357e7d2d61694772bcdd84c70f7035be9ac5..9e47c88437576755cf1c83b9c81ff6bf922a6cd0 100644 --- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js +++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js @@ -79,6 +79,7 @@ // 'as/dto/vocabulary/id/IVocabularyId', // 'as/dto/vocabulary/id/IVocabularyTermId', // 'as/dto/roleassignment/id/RoleAssignmentTechId', + // 'as/dto/rights/Right', // these are the DTOs that can be "manually" created on the client @@ -539,6 +540,11 @@ var sources = [ 'as/dto/roleassignment/RoleLevel', 'as/dto/roleassignment/RoleAssignment', + 'as/dto/rights/fetchoptions/RightsFetchOptions', + 'as/dto/rights/get/GetRightsOperation', + 'as/dto/rights/get/GetRightsOperationResult', + 'as/dto/rights/Rights', + 'as/dto/plugin/create/PluginCreation', 'as/dto/plugin/create/CreatePluginsOperation', 'as/dto/plugin/create/CreatePluginsOperationResult', diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js index 0a42cbbdbad41033fb37180a53ab0c500162caf4..a6702146e7f7fcde5bab4823d765ad174457d18b 100644 --- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js +++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-get.js @@ -811,6 +811,22 @@ define([ 'jquery', 'underscore', 'openbis', 'test/openbis-execute-operations', ' testGet(c, fCreate, fGet, fGetEmptyFetchOptions, fechOptionsTestConfig); }); + QUnit.test("getRights()", function(assert) { + var c = new common(assert); + var sampleId = new c.SampleIdentifier("/PLATONIC/PLATE-2"); + c.start(); + + c.createFacadeAndLogin().then(function(facade) { + return facade.getRights([sampleId], new c.RightsFetchOptions()).then(function(rights) { + c.assertEqual(rights[sampleId].rights, "UPDATE", "Rights"); + c.finish(); + }); + }).fail(function(error) { + c.fail(error.message); + c.finish(); + }); + }); + QUnit.test("getServerInformation()", function(assert) { var c = new common(assert); c.start(); @@ -827,7 +843,7 @@ define([ 'jquery', 'underscore', 'openbis', 'test/openbis-execute-operations', ' c.finish(); }); }); - + } return function() { diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java index 60eb28860824f30e612a52ca9346968e15ca3969..6defe39004a113aa2a4efad4a246cd8866983c5e 100644 --- a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java +++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/spring/MarkerLogApplicationListener.java @@ -16,6 +16,9 @@ package ch.systemsx.cisd.openbis.common.spring; +import java.io.File; +import java.io.IOException; + import org.apache.log4j.Logger; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @@ -28,13 +31,15 @@ import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; /** - * An application listener, whose sole purpose is to log a marker message immediately after the successful start/stop of the openBIS application - * server. + * An application listener, whose sole purpose is to log a marker message and create a marker file + * immediately after the successful start/stop of the openBIS application server. * * @author Kaloyan Enimanev */ public class MarkerLogApplicationListener implements ApplicationListener { + private static final File STARTED_FILE = new File("SERVER_STARTED"); + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, MarkerLogApplicationListener.class); @@ -52,10 +57,20 @@ public class MarkerLogApplicationListener implements ApplicationListener // root application context has been initialized } else { - // print a marker string to make it possible + // print a marker string and create marker file to make it possible // for log-analyzing software to determine when the application is up and // running operationLog.info("SERVER STARTED"); + try + { + STARTED_FILE.createNewFile(); + STARTED_FILE.deleteOnExit(); + operationLog.info(STARTED_FILE.getAbsolutePath()+" created"); + } catch (IOException ex) + { + operationLog.error("Couldn't create marker file " + STARTED_FILE, ex); + } + } } else if (isStoppingEvent(event)) { diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java index 0d82e6edafe0fdaa86bdd7aa9d4740b0c3647f5c..e36f64ac0392565ab1e0c1c33d4cf83fbf65fb5a 100644 --- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java @@ -44,6 +44,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.Update import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetServerInformationOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetServerInformationOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; @@ -298,6 +299,10 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.SearchQueriesOperat import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.SearchQueriesOperationResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.UpdateQueriesOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperationResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.CreateRoleAssignmentsOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.CreateRoleAssignmentsOperationResult; @@ -851,6 +856,14 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi> executeOperation(sessionToken, new UpdateSemanticAnnotationsOperation(updates)); } + @Override + @Transactional(readOnly = true) + public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions) + { + GetRightsOperationResult result = executeOperation(sessionToken, new GetRightsOperation(ids, fetchOptions)); + return result.getObjectMap(); + } + @Override @Transactional(readOnly = true) public Map<ISpaceId, Space> getSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceFetchOptions fetchOptions) diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java index 97316ab2f980c5894a0fce8243f4d0b3ec5747b8..152e2bbd35ca5cb9e6b099e06a4788ce6b6d6d77 100644 --- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java @@ -29,6 +29,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.id.IAuthoriza import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.AuthorizationGroupUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet; @@ -153,6 +154,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.IQueryId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.QueryTechId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.QuerySearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.RoleAssignmentCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.delete.RoleAssignmentDeletionOptions; @@ -554,6 +557,13 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements logAccess(sessionToken, "update-semantic-annotations", "SEMANTIC_ANNOTATION_UPDATES(%s)", abbreviate(annotationUpdates)); } + @Override + public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions) + { + logAccess(sessionToken, "get-rights", "IDS(%s) FETCH_OPTIONS(%s)", abbreviate(ids), fetchOptions); + return null; + } + @Override public Map<ISpaceId, Space> getSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceFetchOptions fetchOptions) { diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java index 8145b64a39a72bc758879da3ef2de82fe8b7a7d8..61a44a25b8d8e19798399af9a7dcb211d8691b4a 100644 --- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java @@ -117,6 +117,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IExecuteSqlOpe import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IGetQueriesOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.ISearchQueriesOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.query.IUpdateQueriesOperationExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.rights.IGetRightsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.ICreateRoleAssignmentsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.IDeleteRoleAssignmentsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.roleassignment.IGetRoleAssignmentsOperationExecutor; @@ -392,8 +393,11 @@ public class OperationsExecutor implements IOperationsExecutor private IInternalOperationExecutor internalOperationExecutor; @Autowired - private IGetSpacesOperationExecutor getSpacesExecutor; + private IGetRightsOperationExecutor getRightsExecutor; + @Autowired + private IGetSpacesOperationExecutor getSpacesExecutor; + @Autowired private IGetProjectsOperationExecutor getProjectsExecutor; @@ -694,6 +698,7 @@ public class OperationsExecutor implements IOperationsExecutor private void executeGets(List<? extends IOperation> operations, Map<IOperation, IOperationResult> resultMap, IOperationContext context) { + resultMap.putAll(getRightsExecutor.execute(context, operations)); resultMap.putAll(getSpacesExecutor.execute(context, operations)); resultMap.putAll(getProjectsExecutor.execute(context, operations)); resultMap.putAll(getExperimentsExecutor.execute(context, operations)); diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..9a2734fed80916da15e831b4a5470403bae71dbf --- /dev/null +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsExecutor.java @@ -0,0 +1,215 @@ +/* + * 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.ethz.sis.openbis.generic.server.asapi.v3.executor.rights; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.IDataSetId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.IExperimentId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Right; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.dataset.IDataSetAuthorizationExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.dataset.IMapDataSetByIdExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IExperimentAuthorizationExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IMapExperimentByIdExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.sample.IMapSampleByIdExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.sample.ISampleAuthorizationExecutor; +import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE; +import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE; +import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE; + +/** + * @author Franz-Josef Elmer + */ +@Component +public class GetRightsExecutor implements IGetRightsExecutor +{ + @Autowired + private IMapSampleByIdExecutor mapSampleByIdExecutor; + + @Autowired + private ISampleAuthorizationExecutor sampleAuthorizationExecutor; + + @Autowired + private IMapExperimentByIdExecutor mapExperimentByIdExecutor; + + @Autowired + private IExperimentAuthorizationExecutor experimentAuthorizationExecutor; + + @Autowired + private IMapDataSetByIdExecutor mapDataSetByIdExecutor; + + @Autowired + private IDataSetAuthorizationExecutor dataSetAuthorizationExecutor; + + private List<Handler> handlers; + + public GetRightsExecutor() + { + handlers = new ArrayList<>(); + handlers.add(new SampleHandler()); + handlers.add(new ExperimentHandler()); + handlers.add(new DataSetHandler()); + } + + @Override + public Map<IObjectId, Rights> getRights(IOperationContext context, List<? extends IObjectId> objectIds, RightsFetchOptions fetchOptions) + { + Map<IObjectId, Rights> result = new HashMap<>(); + for (IObjectId id : objectIds) + { + for (Handler handler : handlers) + { + handler.handle(id); + } + } + for (Handler handler : handlers) + { + handler.addRights(context, result); + } + return result; + } + + private static interface Handler + { + void handle(IObjectId id); + + void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds); + } + + private class SampleHandler implements Handler + { + private List<ISampleId> sampleIds = new ArrayList<>(); + + @Override + public void handle(IObjectId id) + { + if (id instanceof ISampleId) + { + sampleIds.add((ISampleId) id); + } + } + + @Override + public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds) + { + Map<ISampleId, SamplePE> map = mapSampleByIdExecutor.map(context, sampleIds); + for (Entry<ISampleId, SamplePE> entry : map.entrySet()) + { + Set<Right> rights = new HashSet<>(); + ISampleId id = entry.getKey(); + SamplePE object = entry.getValue(); + + try + { + sampleAuthorizationExecutor.canUpdate(context, id, object); + rights.add(Right.UPDATE); + } catch (AuthorizationFailureException e) + { + // silently ignored + } + rightsByIds.put(id, new Rights(rights)); + } + } + } + + private class ExperimentHandler implements Handler + { + private List<IExperimentId> experimentIds = new ArrayList<>(); + + @Override + public void handle(IObjectId id) + { + if (id instanceof IExperimentId) + { + experimentIds.add((IExperimentId) id); + } + } + + @Override + public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds) + { + Map<IExperimentId, ExperimentPE> map = mapExperimentByIdExecutor.map(context, experimentIds); + for (Entry<IExperimentId, ExperimentPE> entry : map.entrySet()) + { + Set<Right> rights = new HashSet<>(); + IExperimentId id = entry.getKey(); + ExperimentPE object = entry.getValue(); + + try + { + experimentAuthorizationExecutor.canUpdate(context, id, object); + rights.add(Right.UPDATE); + } catch (AuthorizationFailureException e) + { + // silently ignored + } + rightsByIds.put(id, new Rights(rights)); + } + } + } + + private class DataSetHandler implements Handler + { + private List<IDataSetId> dataSetIds = new ArrayList<>(); + + @Override + public void handle(IObjectId id) + { + if (id instanceof IDataSetId) + { + dataSetIds.add((IDataSetId) id); + } + } + + @Override + public void addRights(IOperationContext context, Map<IObjectId, Rights> rightsByIds) + { + Map<IDataSetId, DataPE> map = mapDataSetByIdExecutor.map(context, dataSetIds); + for (Entry<IDataSetId, DataPE> entry : map.entrySet()) + { + Set<Right> rights = new HashSet<>(); + IDataSetId id = entry.getKey(); + DataPE object = entry.getValue(); + + try + { + dataSetAuthorizationExecutor.canUpdate(context, id, object); + rights.add(Right.UPDATE); + } catch (AuthorizationFailureException e) + { + // silently ignored + } + rightsByIds.put(id, new Rights(rights)); + } + } + } +} diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..7d34b9c783e1b2bf616dc25012c6a86b372c5491 --- /dev/null +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/GetRightsOperationExecutor.java @@ -0,0 +1,54 @@ +/* + * 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.ethz.sis.openbis.generic.server.asapi.v3.executor.rights; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.get.GetRightsOperationResult; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.OperationExecutor; + +/** + * @author Franz-Josef Elmer + */ +@Component +public class GetRightsOperationExecutor extends OperationExecutor<GetRightsOperation, GetRightsOperationResult> + implements IGetRightsOperationExecutor +{ + @Autowired + private IGetRightsExecutor executor; + + @Override + protected Class<? extends GetRightsOperation> getOperationClass() + { + return GetRightsOperation.class; + } + + @Override + protected GetRightsOperationResult doExecute(IOperationContext context, GetRightsOperation operation) + { + Map<IObjectId, Rights> map = executor.getRights(context, operation.getObjectIds(), operation.getFetchOptions()); + return new GetRightsOperationResult(map); + } + +} diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..9e3d1a3b66a37d8f2f137796acd7a37ca64eb9d3 --- /dev/null +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsExecutor.java @@ -0,0 +1,34 @@ +/* + * 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.ethz.sis.openbis.generic.server.asapi.v3.executor.rights; + +import java.util.List; +import java.util.Map; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; + +/** + * @author Franz-Josef Elmer + */ +public interface IGetRightsExecutor +{ + + public Map<IObjectId, Rights> getRights(IOperationContext context, List<? extends IObjectId> objectIds, RightsFetchOptions fetchOptions); +} diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..f1acb263ae00121d27a2c680b6511aa8570ba129 --- /dev/null +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/rights/IGetRightsOperationExecutor.java @@ -0,0 +1,28 @@ +/* + * 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.ethz.sis.openbis.generic.server.asapi.v3.executor.rights; + +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.IOperationExecutor; + +/** + * @author Franz-Josef Elmer + * + */ +public interface IGetRightsOperationExecutor extends IOperationExecutor +{ + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java index f2ea8c6ddce82e2aac1cd5ef716a162bbb53a84b..1b612dc9f08d5d26f3030dba393f5ae93e320446 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/hotfix/ELNCollectionTypeMigration.java @@ -18,9 +18,13 @@ import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; import ch.systemsx.cisd.openbis.generic.server.ComponentNames; import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.DAOFactory; import org.hibernate.Session; +import org.hibernate.internal.SessionImpl; import org.hibernate.query.NativeQuery; import java.math.BigInteger; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.*; @@ -88,6 +92,11 @@ public class ELNCollectionTypeMigration { "UPDATE property_types SET code = 'EXPERIMENTAL_STEP.EXPERIMENTAL_DESCRIPTION' WHERE code = 'EXPERIMENTAL_PROCEDURE' and (select count(*) from core_plugins where name = 'eln-lims') > 0;" }; + private static final String[] WIDGET_POST_UPDATES = new String[]{ + "UPDATE property_types SET meta_data = '{ \"custom_widget\" : \"Word Processor\" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR'));", + "UPDATE property_types SET meta_data = NULL WHERE id IN (SELECT id FROM property_types WHERE daty_id IN (SELECT id FROM data_types WHERE code NOT IN ('MULTILINE_VARCHAR', 'XML')));" + }; + private static Set<ExperimentType> getExperimentTypes(String[] experimentCodes) { IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); String sessionToken = api.loginAsSystem(); @@ -189,6 +198,18 @@ public class ELNCollectionTypeMigration { nativeQuery.executeUpdate(); } + private static void executeNativeUpdate(String SQL) { + try { + DAOFactory daoFactory = (DAOFactory) CommonServiceProvider.getApplicationContext().getBean(ComponentNames.DAO_FACTORY); + Session currentSession = daoFactory.getSessionFactory().getCurrentSession(); + Connection connection = ((SessionImpl) currentSession).connection(); + PreparedStatement statement = connection.prepareStatement(SQL); + statement.execute(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + private static Map<BigInteger, BigInteger> getMap(List<Object[]> prty_id_AND_etpt_id) { Map<BigInteger, BigInteger> prty_id_2_etpt_id = new HashMap(); for (Object[] row:prty_id_AND_etpt_id) { @@ -197,8 +218,8 @@ public class ELNCollectionTypeMigration { return prty_id_2_etpt_id; } - public static void migrate() { - System.out.println("ELNCollectionTypeMigration START"); + public static void beforeUpgrade() { + System.out.println("ELNCollectionTypeMigration beforeUpgrade START"); // Obtain property types used by experiments that should be of type COLLECTION for (String experimentCode:experimentsOfTypeCollection) { Set<ExperimentType> experimentTypes = getExperimentTypes(new String[]{experimentCode}); @@ -261,6 +282,16 @@ public class ELNCollectionTypeMigration { executeUpdate(PROPERTY_UPDATE, null, null, null, null); System.out.println("PROPERTY_UPDATE DONE"); } - System.out.println("ELNCollectionTypeMigration END"); + System.out.println("ELNCollectionTypeMigration beforeUpgrade END"); + } + + public static void afterUpgrade() { + System.out.println("ELNCollectionTypeMigration afterUpgrade START"); + for (String WIDGET_POST_UPDATE:WIDGET_POST_UPDATES) { + System.out.println("Going to Execute WIDGET_POST_UPDATES: " + WIDGET_POST_UPDATE); + executeNativeUpdate(WIDGET_POST_UPDATE); + System.out.println("WIDGET_POST_UPDATE DONE"); + } + System.out.println("ELNCollectionTypeMigration afterUpgrade END"); } } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js new file mode 100644 index 0000000000000000000000000000000000000000..b9aa090e7d4b5675710ffeaead1525cf595bd063 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Right.js @@ -0,0 +1,8 @@ +define([ "stjs", "as/dto/common/Enum" ], function(stjs, Enum) { + var Right = function() { + Enum.call(this, [ "UPDATE", "CREATE_SAMPLE" ]); + }; + stjs.extend(Right, Enum, [ Enum ], function(constructor, prototype) { + }, {}); + return new Right(); +}) \ No newline at end of file diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js new file mode 100644 index 0000000000000000000000000000000000000000..803b23bf28a867ba4dd2a5460f5a13ed4a335f4c --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/Rights.js @@ -0,0 +1,22 @@ +define(["stjs"], function(stjs) { + var Rights = function() { + }; + stjs.extend(Rights, null, [], function(constructor, prototype) { + prototype["@type"] = 'as.dto.rights.Rights'; + constructor.serialVersionUID = 1; + prototype.rights = null; + + prototype.getRights = function() { + return this.rights; + }; + prototype.setRights = function(rights) { + this.rights = rights; + }; + }, { + rights : { + name : "Set", + arguments : [ "Right"] + } + }); + return Rights; +}) \ No newline at end of file diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js new file mode 100644 index 0000000000000000000000000000000000000000..f5043caad3a1de25fe1e20f756d27df7da52f30b --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/fetchoptions/RightsFetchOptions.js @@ -0,0 +1,9 @@ +define([ "stjs", "as/dto/common/fetchoptions/EmptyFetchOptions" ], function(stjs, EmptyFetchOptions) { + var RightsFetchOptions = function() { + }; + stjs.extend(RightsFetchOptions, EmptyFetchOptions, [ EmptyFetchOptions ], function(constructor, prototype) { + prototype['@type'] = 'as.dto.rights.fetchoptions.RightsFetchOptions'; + constructor.serialVersionUID = 1; + }, {}); + return RightsFetchOptions; +}) diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js new file mode 100644 index 0000000000000000000000000000000000000000..fa136600e3e41d7ae8391c09f00de31fdd7af421 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperation.js @@ -0,0 +1,12 @@ +define([ "stjs", "as/dto/common/get/GetObjectsOperation" ], function(stjs, GetObjectsOperation) { + var GetRightsOperation = function(objectIds, fetchOptions) { + GetObjectsOperation.call(this, objectIds, fetchOptions); + }; + stjs.extend(GetRightsOperation, GetObjectsOperation, [ GetObjectsOperation ], function(constructor, prototype) { + prototype['@type'] = 'as.dto.rights.get.GetRightsOperation'; + prototype.getMessage = function() { + return "GetRightsOperation"; + }; + }, {}); + return GetRightsOperation; +}) diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js new file mode 100644 index 0000000000000000000000000000000000000000..60991ee65f1c24c44b2bf63ec846ea89deae8165 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/rights/get/GetRightsOperationResult.js @@ -0,0 +1,12 @@ +define([ "stjs", "as/dto/common/get/GetObjectsOperationResult" ], function(stjs, GetObjectsOperationResult) { + var GetRightsOperationResult = function(objectMap) { + GetObjectsOperationResult.call(this, objectMap); + }; + stjs.extend(GetRightsOperationResult, GetObjectsOperationResult, [ GetObjectsOperationResult ], function(constructor, prototype) { + prototype['@type'] = 'as.dto.rights.get.GetRightsOperationResult'; + prototype.getMessage = function() { + return "GetRightsOperationResult"; + }; + }, {}); + return GetRightsOperationResult; +}) 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 672ac400beb4e5427f8154a07f98d97679c906e2..9143e0a045f85a94cde9033f34dce92d60709bbf 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 @@ -904,6 +904,21 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria }); } + this.getRights = function(ids, fetchOptions) { + var thisFacade = this; + return thisFacade._private.ajaxRequest({ + url : openbisUrl, + data : { + "method" : "getRights", + "params" : [ thisFacade._private.sessionToken, ids, fetchOptions ] + }, + returnType : { + name : "Map", + arguments : [ "IObjectId", "Rights" ] + } + }); + } + this.getSpaces = function(ids, fetchOptions) { var thisFacade = this; return thisFacade._private.ajaxRequest({ @@ -918,7 +933,7 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria } }); } - + this.getProjects = function(ids, fetchOptions) { var thisFacade = this; return thisFacade._private.ajaxRequest({ diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d7245ac439b160245894950d951ff4bca8168176 --- /dev/null +++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/GetRightsTest.java @@ -0,0 +1,85 @@ +/* + * 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.ethz.sis.openbis.systemtest.asapi.v3; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Map; + +import org.testng.annotations.Test; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentIdentifier; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier; + +/** + * @author Franz-Josef Elmer + */ +public class GetRightsTest extends AbstractTest +{ + @Test + public void testGetSampleRights() + { + // Given + String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD); + IObjectId s1 = new SampleIdentifier("/TEST-SPACE/CP-TEST-4"); + IObjectId s2 = new SampleIdentifier("/CISD/CP-TEST-1"); + + // When + Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions()); + + // Then + assertEquals(map.get(s1).getRights().toString(), "[]"); + assertEquals(map.get(s2).getRights().toString(), "[UPDATE]"); + } + + @Test + public void testGetExperimentRights() + { + // Given + String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD); + IObjectId s1 = new ExperimentIdentifier("/TEST-SPACE/TEST-PROJECT/EXP-SPACE-TEST"); + IObjectId s2 = new ExperimentIdentifier("/CISD/NEMO/EXP1"); + + // When + Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions()); + + // Then + assertEquals(map.get(s1).getRights().toString(), "[]"); + assertEquals(map.get(s2).getRights().toString(), "[UPDATE]"); + } + + @Test + public void testGetDataSetRights() + { + // Given + String sessionToken = v3api.login(TEST_ROLE_V3, PASSWORD); + IObjectId s1 = new DataSetPermId("20120619092259000-22"); + IObjectId s2 = new DataSetPermId("20081105092259000-21"); + + // When + Map<IObjectId, Rights> map = v3api.getRights(sessionToken, Arrays.asList(s1, s2), new RightsFetchOptions()); + + // Then + assertEquals(map.get(s1).getRights().toString(), "[]"); + assertEquals(map.get(s2).getRights().toString(), "[UPDATE]"); + } +} diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java index 6cb34d1533ce27140dabacd9bfd6b19031d8ed58..a6f1246c4378bb12f5a11a50035831cb86e5ea4b 100644 --- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java @@ -28,6 +28,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.id.IAuthoriza import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.update.AuthorizationGroupUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.TableModel; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet; @@ -152,6 +153,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.IQueryId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.id.QueryTechId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.search.QuerySearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.query.update.QueryUpdate; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.RoleAssignment; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.create.RoleAssignmentCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.delete.RoleAssignmentDeletionOptions; @@ -771,6 +774,14 @@ public interface IApplicationServerApi extends IRpcService */ public void updateQueries(String sessionToken, List<QueryUpdate> queryUpdates); + /** + * Gets authorization rights for the provided {@link IObjectId} ids. A result map contains an entry for a given id + * only if an object for that id has been found and that object can be accessed by the user. + * + * @throws UserFailureException in case of any problems + */ + public Map<IObjectId, Rights> getRights(String sessionToken, List<? extends IObjectId> ids, RightsFetchOptions fetchOptions); + /** * Gets spaces for the provided {@code ISpaceId} ids. A result map contains an entry for a given id only if a space for that id has been found and * that space can be accessed by the user. diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java new file mode 100644 index 0000000000000000000000000000000000000000..6cc6dcd9abe33f10ad25a2f69c3630da8ecb3b78 --- /dev/null +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Right.java @@ -0,0 +1,28 @@ +/* + * 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.ethz.sis.openbis.generic.asapi.v3.dto.rights; + +import ch.systemsx.cisd.base.annotation.JsonObject; + +/** + * @author Franz-Josef Elmer + */ +@JsonObject("as.dto.rights.Right") +public enum Right +{ + UPDATE, CREATE_SAMPLE +} diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java new file mode 100644 index 0000000000000000000000000000000000000000..14e612b7ac8f55dbf4136ee523b75338242a3f63 --- /dev/null +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/Rights.java @@ -0,0 +1,63 @@ +/* + * 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.ethz.sis.openbis.generic.asapi.v3.dto.rights; + +import java.io.Serializable; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import ch.systemsx.cisd.base.annotation.JsonObject; + +/** + * @author Franz-Josef Elmer + */ +@JsonObject("as.dto.rights.Rights") +public class Rights implements Serializable +{ + private static final long serialVersionUID = 1L; + + @JsonProperty + private Set<Right> rights; + + @SuppressWarnings("unused") + private Rights() + { + } + + public Rights(Set<Right> rights) + { + this.rights = rights; + } + + public Set<Right> getRights() + { + return rights; + } + + public void setRights(Set<Right> rights) + { + this.rights = rights; + } + + @Override + public String toString() + { + return "Rights: " + rights; + } + +} diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..952d1abd691d0f73f94f62a65bff72d9c13b4d5d --- /dev/null +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/fetchoptions/RightsFetchOptions.java @@ -0,0 +1,30 @@ +/* + * 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.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.fetchoptions.EmptyFetchOptions; +import ch.systemsx.cisd.base.annotation.JsonObject; + +/** + * @author Franz-Josef Elmer + * + */ +@JsonObject("as.dto.rights.fetchoptions.RightsFetchOptions") +public class RightsFetchOptions extends EmptyFetchOptions +{ + private static final long serialVersionUID = 1L; +} diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..c7049d1e333c242cfaa3020ce625e93e2bf150c0 --- /dev/null +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperation.java @@ -0,0 +1,43 @@ +/* + * 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.ethz.sis.openbis.generic.asapi.v3.dto.rights.get; + +import java.util.List; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetObjectsOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.fetchoptions.RightsFetchOptions; +import ch.systemsx.cisd.base.annotation.JsonObject; + +/** + * @author Franz-Josef Elmer + */ +@JsonObject("as.dto.rights.get.GetRightsOperation") +public class GetRightsOperation extends GetObjectsOperation<IObjectId, RightsFetchOptions> +{ + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unused") + private GetRightsOperation() + { + } + + public GetRightsOperation(List<? extends IObjectId> ids, RightsFetchOptions fetchOptions) + { + super(ids, fetchOptions); + } +} \ No newline at end of file diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..2e3bc7b0d9f518e16be036347563fde3c2b26a82 --- /dev/null +++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/rights/get/GetRightsOperationResult.java @@ -0,0 +1,45 @@ +/* + * 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.ethz.sis.openbis.generic.asapi.v3.dto.rights.get; + +import java.util.Map; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.get.GetObjectsOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.systemsx.cisd.base.annotation.JsonObject; + +/** + * @author Franz-Josef Elmer + * + */ +@JsonObject("as.dto.rights.get.GetRightsOperationResult") +public class GetRightsOperationResult extends GetObjectsOperationResult<IObjectId, Rights> +{ + + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unused") + private GetRightsOperationResult() + { + } + + public GetRightsOperationResult(Map<IObjectId, Rights> objectMap) + { + super(objectMap); + } +} diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt index 002d2d0c0b66fe9f7f02de0a45830a6ba9447794..d789dda8868bd61bd555440bc7041ccfe56a76d0 100644 --- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt +++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt @@ -2322,3 +2322,12 @@ get Meta Data set Meta Data set Meta Data Actions +get Rights +Get Rights Operation +Get Rights Operation Result +Right +Rights +Rights Fetch Options +set Rights +CREATE_SAMPLE + diff --git a/openbis_ng_ui/package.json b/openbis_ng_ui/package.json index 2ebec20c513a647d3b9291b8f888a3ddcaf5b0d0..074ed6cbb6f59e342288b496997ceb4660b6841a 100644 --- a/openbis_ng_ui/package.json +++ b/openbis_ng_ui/package.json @@ -12,6 +12,7 @@ "npm": "^6.11.3", "path-to-regexp": "^3.1.0", "prop-types": "^15.7.2", + "re-resizable": "^6.1.0", "react": "^16.10.1", "react-beautiful-dnd": "^11.0.5", "react-dom": "^16.10.1", diff --git a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx index b5c1f23e5757dddc2aa8431da7e91c6d8ff9c8f9..6bf2924c95ae6e1959c363301d26d2f156a0778c 100644 --- a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx +++ b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx @@ -1,6 +1,7 @@ import React from 'react' import _ from 'lodash' import Paper from '@material-ui/core/Paper' +import { Resizable } from 're-resizable' import { connect } from 'react-redux' import { withStyles } from '@material-ui/core/styles' import logger from '../../../common/logger.js' @@ -11,10 +12,12 @@ import FilterField from './../form/FilterField.jsx' import BrowserNodes from './BrowserNodes.jsx' const styles = { - container: { + resizable: { display: 'flex', - flexDirection: 'column', - minWidth: '300px' + flexDirection: 'column' + }, + paper: { + height: '100%' } } @@ -59,19 +62,33 @@ class Browser extends React.PureComponent { const classes = this.props.classes return ( - <Paper square={true} elevation={3} classes={{ root: classes.container }}> - <FilterField - filter={this.props.filter} - filterChange={this.props.filterChange} - /> - <BrowserNodes - nodes={this.props.nodes} - nodeSelect={this.props.nodeSelect} - nodeExpand={this.props.nodeExpand} - nodeCollapse={this.props.nodeCollapse} - level={0} - /> - </Paper> + <Resizable + enable={{ + right: true, + left: false, + top: false, + bottom: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false + }} + className={classes.resizable} + > + <Paper square={true} elevation={3} classes={{ root: classes.paper }}> + <FilterField + filter={this.props.filter} + filterChange={this.props.filterChange} + /> + <BrowserNodes + nodes={this.props.nodes} + nodeSelect={this.props.nodeSelect} + nodeExpand={this.props.nodeExpand} + nodeCollapse={this.props.nodeCollapse} + level={0} + /> + </Paper> + </Resizable> ) } } diff --git a/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx b/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..62a39657b87ca1a54c520f47536faa70c1fb41f0 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/CheckboxField.jsx @@ -0,0 +1,23 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Checkbox from '@material-ui/core/Checkbox' +import FormField from './FormField.jsx' +import logger from '../../../common/logger.js' + +const styles = () => ({}) + +class CheckboxFormField extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'CheckboxFormField.render') + + const { value, disabled } = this.props + + return ( + <FormField {...this.props}> + <Checkbox checked={value} disabled={disabled} /> + </FormField> + ) + } +} + +export default withStyles(styles)(CheckboxFormField) diff --git a/openbis_ng_ui/src/js/components/common/form/FormField.jsx b/openbis_ng_ui/src/js/components/common/form/FormField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4b605bcc1e929c0553fe8363e46c5c4281f0ece7 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/FormField.jsx @@ -0,0 +1,88 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import FormControl from '@material-ui/core/FormControl' +import FormHelperText from '@material-ui/core/FormHelperText' +import Typography from '@material-ui/core/Typography' +import logger from '../../../common/logger.js' + +const styles = theme => ({ + labelContainer: { + display: 'flex', + alignItems: 'stretch', + '& div': { + flex: '0 0 auto', + paddingRight: theme.spacing(1) + }, + '& b': { + fontWeight: 'bold', + color: theme.palette.error.main + }, + '& pre': { + flex: '0 0 auto', + margin: 0, + marginLeft: theme.spacing(1), + color: theme.palette.grey.main + } + }, + controlContainer: { + width: '100%' + } +}) + +class FormField extends React.PureComponent { + constructor(props) { + super(props) + } + + render() { + logger.log(logger.DEBUG, 'FormField.render') + + const { description, onClick, styles = {} } = this.props + + return ( + <div onClick={onClick} className={styles.container}> + <FormControl fullWidth={true}> + <Typography component='label'>{this.renderLabel()}</Typography> + <div>{this.renderControl()}</div> + <FormHelperText> + <span data-part='description' className={styles.description}> + {description} + </span> + </FormHelperText> + </FormControl> + </div> + ) + } + + renderLabel() { + const { label, mandatory, metadata, classes, styles = {} } = this.props + return ( + <div className={classes.labelContainer}> + <div> + <span data-part='label' className={styles.label}> + {label} + </span>{' '} + {mandatory && ( + <b data-part='mandatory' className={styles.mandatory}> + * + </b> + )} + </div> + <pre data-part='metadata' className={styles.metadata}> + {metadata} + </pre> + </div> + ) + } + + renderControl() { + const { children, classes, styles = {} } = this.props + return ( + <div data-part='control' className={classes.controlContainer}> + <div className={styles.control}>{children}</div> + </div> + ) + } +} + +export default withStyles(styles)(FormField) diff --git a/openbis_ng_ui/src/js/components/common/form/SelectField.jsx b/openbis_ng_ui/src/js/components/common/form/SelectField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6bf390ed039c6ae9b7cdac06bca5c22707a36b94 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/SelectField.jsx @@ -0,0 +1,31 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import TextField from '@material-ui/core/TextField' +import MenuItem from '@material-ui/core/MenuItem' +import FormField from './FormField.jsx' +import logger from '../../../common/logger.js' + +const styles = () => ({}) + +class SelectFormField extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'SelectFormField.render') + + const { value, disabled, options } = this.props + + return ( + <FormField {...this.props}> + <TextField select value={value} disabled={disabled} fullWidth={true}> + {options && + options.map(option => ( + <MenuItem key={option.value} value={option.value}> + {option.label || option.value} + </MenuItem> + ))} + </TextField> + </FormField> + ) + } +} + +export default withStyles(styles)(SelectFormField) diff --git a/openbis_ng_ui/src/js/components/common/form/TextField.jsx b/openbis_ng_ui/src/js/components/common/form/TextField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..40e3b611f2e2e32889e1551c9482e26c945d0810 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/TextField.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import TextField from '@material-ui/core/TextField' +import FormField from './FormField.jsx' +import logger from '../../../common/logger.js' + +const styles = () => ({}) + +class SelectFormField extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'SelectFormField.render') + + const { type, value, disabled } = this.props + + return ( + <FormField {...this.props}> + <TextField + type={type} + value={value} + disabled={disabled} + fullWidth={true} + /> + </FormField> + ) + } +} + +export default withStyles(styles)(SelectFormField) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx index 2fe17b80c545825ce5fcf6fa6bfd2692f1d08de2..6ac80111e6609176597d704668b9ee0f4db51443 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectType.jsx @@ -1,6 +1,7 @@ import _ from 'lodash' import React from 'react' import { withStyles } from '@material-ui/core/styles' +import { Resizable } from 're-resizable' import ObjectTypePreview from './ObjectTypePreview.jsx' import ObjectTypeParameters from './ObjectTypeParameters.jsx' import ObjectTypeButtons from './ObjectTypeButtons.jsx' @@ -44,6 +45,10 @@ class ObjectType extends React.PureComponent { this.handleOrderChange = this.handleOrderChange.bind(this) this.handleSelectionChange = this.handleSelectionChange.bind(this) this.handleChange = this.handleChange.bind(this) + this.handleAddSection = this.handleAddSection.bind(this) + this.handleAddProperty = this.handleAddProperty.bind(this) + this.handleRemove = this.handleRemove.bind(this) + this.handleSave = this.handleSave.bind(this) } componentDidMount() { @@ -63,27 +68,16 @@ class ObjectType extends React.PureComponent { subcodeUnique: loadedType.subcodeUnique } - const sections = loadedType.propertyAssignments.reduce( - (sections, assignment, index) => { - let section = sections[sections.length - 1] - if (section.name === assignment.section) { - section.properties.push('property-' + index) - } else { - let newSection = { - id: 'section-' + sections.length, - name: assignment.section, - properties: ['property-' + index] - } - sections.push(newSection) - } - return sections - }, - [{ id: 'section-0', name: null, properties: [] }] - ) + const sections = [] + const properties = [] + let currentSection = null + let currentProperty = null + let sectionsCounter = 0 + let propertiesCounter = 0 - const properties = loadedType.propertyAssignments.map( - (assignment, index) => ({ - id: 'property-' + index, + loadedType.propertyAssignments.forEach(assignment => { + currentProperty = { + id: 'property-' + propertiesCounter++, code: assignment.propertyType.code, label: assignment.propertyType.label, description: assignment.propertyType.description, @@ -95,16 +89,31 @@ class ObjectType extends React.PureComponent { ? assignment.propertyType.materialType.code : null, visible: assignment.showInEditView, - mandatory: assignment.mandatory, - section: _.find(sections, ['name', assignment.section]).id - }) - ) + mandatory: assignment.mandatory + } + + if (currentSection && currentSection.name === assignment.section) { + currentSection.properties.push(currentProperty.id) + } else { + currentSection = { + id: 'section-' + sectionsCounter++, + name: assignment.section, + properties: [currentProperty.id] + } + sections.push(currentSection) + } + currentProperty.section = currentSection.id + + properties.push(currentProperty) + }) this.setState(() => ({ loaded: true, type, properties, + propertiesCounter, sections, + sectionsCounter, selection: null })) }) @@ -289,6 +298,184 @@ class ObjectType extends React.PureComponent { })) } + handleAddSection() { + let { sections, sectionsCounter, selection } = this.state + + let newSections = Array.from(sections) + let newSection = { + id: 'section-' + sectionsCounter++, + name: null, + properties: [] + } + let newSelection = { + type: 'section', + params: { + id: newSection.id + } + } + + if (selection) { + if (selection.type === 'section') { + let index = sections.findIndex( + section => section.id === selection.params.id + ) + newSections.splice(index + 1, 0, newSection) + } else if (selection.type === 'property') { + let index = sections.findIndex( + section => section.properties.indexOf(selection.params.id) !== -1 + ) + newSections.splice(index + 1, 0, newSection) + } else { + newSections.push(newSection) + } + } else { + newSections.push(newSection) + } + + this.setState(state => ({ + ...state, + sections: newSections, + sectionsCounter, + selection: newSelection + })) + } + + handleAddProperty() { + let { sections, properties, propertiesCounter, selection } = this.state + + let sectionIndex = null + let sectionPropertyIndex = null + let propertyIndex = null + + if (selection.type === 'section') { + sectionIndex = sections.findIndex( + section => section.id === selection.params.id + ) + sectionPropertyIndex = sections[sectionIndex].properties.length + propertyIndex = properties.length + } else if (selection.type === 'property') { + sections.forEach((section, i) => { + section.properties.forEach((property, j) => { + if (property === selection.params.id) { + sectionIndex = i + sectionPropertyIndex = j + 1 + } + }) + }) + propertyIndex = + properties.findIndex(property => property.id === selection.params.id) + + 1 + } + + let section = sections[sectionIndex] + + let newProperties = Array.from(properties) + let newProperty = { + id: 'property-' + propertiesCounter++, + code: '', + label: '', + description: '', + dataType: 'VARCHAR', + vocabulary: null, + materialType: null, + visible: true, + mandatory: false, + section: section.id + } + newProperties.splice(propertyIndex, 0, newProperty) + + let newSection = { + ...section, + properties: Array.from(section.properties) + } + newSection.properties.splice(sectionPropertyIndex, 0, newProperty.id) + + let newSections = Array.from(sections) + newSections[sectionIndex] = newSection + + let newSelection = { + type: 'property', + params: { + id: newProperty.id + } + } + + this.setState(state => ({ + ...state, + sections: newSections, + properties: newProperties, + propertiesCounter, + selection: newSelection + })) + } + + handleRemove() { + const { selection } = this.state + + if (selection.type === 'section') { + this.handleRemoveSection(selection.params.id) + } else if (selection.type === 'property') { + this.handleRemoveProperty(selection.params.id) + } + } + + handleRemoveSection(sectionId) { + const { sections, properties } = this.state + + const sectionIndex = sections.findIndex(section => section.id === sectionId) + const section = sections[sectionIndex] + + const newProperties = Array.from(properties) + _.remove( + newProperties, + property => section.properties.indexOf(property.id) !== -1 + ) + + const newSections = Array.from(sections) + newSections.splice(sectionIndex, 1) + + this.setState(state => ({ + ...state, + sections: newSections, + properties: newProperties, + selection: null + })) + } + + handleRemoveProperty(propertyId) { + const { sections, properties } = this.state + + const propertyIndex = properties.findIndex( + property => property.id === propertyId + ) + const property = properties[propertyIndex] + + const newProperties = Array.from(properties) + newProperties.splice(propertyIndex, 1) + + let sectionIndex = sections.findIndex( + section => section.id === property.section + ) + let section = sections[sectionIndex] + let newSection = { + ...section, + properties: Array.from(section.properties) + } + _.remove(newSection.properties, property => property === propertyId) + + const newSections = Array.from(sections) + newSections[sectionIndex] = newSection + + this.setState(state => ({ + ...state, + sections: newSections, + properties: newProperties, + selection: null + })) + } + + handleSave() {} + render() { logger.log(logger.DEBUG, 'ObjectType.render') @@ -313,18 +500,41 @@ class ObjectType extends React.PureComponent { /> </div> <div className={classes.buttons}> - <ObjectTypeButtons /> + <ObjectTypeButtons + onAddSection={this.handleAddSection} + onAddProperty={this.handleAddProperty} + onRemove={this.handleRemove} + onSave={this.handleSave} + addSectionEnabled={true} + addPropertyEnabled={selection !== null} + removeEnabled={selection !== null} + saveEnabled={false} + /> </div> </div> - <div className={classes.parameters}> - <ObjectTypeParameters - type={type} - properties={properties} - sections={sections} - selection={selection} - onChange={this.handleChange} - /> - </div> + <Resizable + enable={{ + left: true, + top: false, + right: false, + bottom: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false + }} + > + <div className={classes.parameters}> + <ObjectTypeParameters + type={type} + properties={properties} + sections={sections} + selection={selection} + onChange={this.handleChange} + onSelectionChange={this.handleSelectionChange} + /> + </div> + </Resizable> </div> ) } diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx index bf55e1d7c58be93ed14e0318c94f39bb0d7c9bc1..2a72b1866847be15d5c2c95692ea48148cf47d25 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeButtons.jsx @@ -17,7 +17,17 @@ class ObjectTypeButtons extends React.PureComponent { render() { logger.log(logger.DEBUG, 'ObjectTypeButtons.render') - const classes = this.props.classes + const { + classes, + onAddSection, + onAddProperty, + onRemove, + onSave, + addSectionEnabled, + addPropertyEnabled, + removeEnabled, + saveEnabled + } = this.props return ( <div className={classes.container}> @@ -25,7 +35,8 @@ class ObjectTypeButtons extends React.PureComponent { classes={{ root: classes.button }} variant='contained' color='secondary' - onClick={this.props.onAddSection} + disabled={!addSectionEnabled} + onClick={onAddSection} > Add Section </Button> @@ -33,15 +44,26 @@ class ObjectTypeButtons extends React.PureComponent { classes={{ root: classes.button }} variant='contained' color='secondary' - onClick={this.props.onAddProperty} + disabled={!addPropertyEnabled} + onClick={onAddProperty} > Add Property </Button> + <Button + classes={{ root: classes.button }} + variant='contained' + color='secondary' + disabled={!removeEnabled} + onClick={onRemove} + > + Remove + </Button> <Button classes={{ root: classes.button }} variant='contained' color='primary' - onClick={this.props.onSave} + disabled={!saveEnabled} + onClick={onSave} > Save </Button> diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx index 8aa4985a05d15d41b1e6d4606f435d4889687ca2..6eeabc6918c48627d4e7695b4096bdf23ddb714c 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParameters.jsx @@ -5,7 +5,11 @@ import ObjectTypeParametersProperty from './ObjectTypeParametersProperty.jsx' import ObjectTypeParametersSection from './ObjectTypeParametersSection.jsx' import logger from '../../../common/logger.js' -const styles = () => ({}) +const styles = () => ({ + container: { + minWidth: '400px' + } +}) class ObjectTypeParameters extends React.PureComponent { constructor(props) { @@ -15,10 +19,18 @@ class ObjectTypeParameters extends React.PureComponent { render() { logger.log(logger.DEBUG, 'ObjectTypeParameters.render') - const { type, sections, properties, selection, onChange } = this.props + const { + type, + sections, + properties, + selection, + onChange, + onSelectionChange, + classes + } = this.props return ( - <React.Fragment> + <div className={classes.container}> <ObjectTypeParametersType type={type} selection={selection} @@ -33,8 +45,9 @@ class ObjectTypeParameters extends React.PureComponent { properties={properties} selection={selection} onChange={onChange} + onSelectionChange={onSelectionChange} /> - </React.Fragment> + </div> ) } } diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx index 64a5890d5288c8009bca010336bc1015bd0af803..9a28a232926c6a771c7e2603d2d84a4a2e13f4d7 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx @@ -18,11 +18,21 @@ class ObjectTypeParametersProperty extends React.PureComponent { constructor(props) { super(props) this.state = {} + this.references = { + code: React.createRef(), + label: React.createRef(), + description: React.createRef(), + dataType: React.createRef(), + mandatory: React.createRef() + } + this.actions = {} this.handleChange = this.handleChange.bind(this) + this.handleFocus = this.handleFocus.bind(this) } componentDidMount() { this.load() + this.focus() } componentDidUpdate(prevProps) { @@ -35,6 +45,13 @@ class ObjectTypeParametersProperty extends React.PureComponent { if (prevDataType !== dataType) { this.load() } + + const prevSelection = prevProps.selection + const selection = this.props.selection + + if (prevSelection !== selection) { + this.focus() + } } load() { @@ -73,6 +90,23 @@ class ObjectTypeParametersProperty extends React.PureComponent { }) } + focus() { + const property = this.getProperty(this.props) + if (property) { + const { part } = this.props.selection.params + if (part) { + const reference = this.references[part] + if (reference) { + reference.current.focus() + } + const action = this.actions[part] + if (action) { + action.focusVisible() + } + } + } + } + handleChange(event) { const property = this.getProperty(this.props) @@ -95,6 +129,26 @@ class ObjectTypeParametersProperty extends React.PureComponent { this.props.onChange('property', params) } + handleFocus(event) { + const property = this.getProperty(this.props) + + let params = null + + if (_.has(event.target, 'checked')) { + params = { + id: property.id, + part: event.target.value + } + } else { + params = { + id: property.id, + part: event.target.name + } + } + + this.props.onSelectionChange('property', params) + } + render() { logger.log(logger.DEBUG, 'ObjectTypeParametersProperty.render') @@ -112,9 +166,10 @@ class ObjectTypeParametersProperty extends React.PureComponent { <form> <div> <TextField - label='Code' - name='code' - value={property.code} + inputRef={this.references.label} + label='Label' + name='label' + value={property.label} fullWidth={true} margin='normal' variant='filled' @@ -122,13 +177,15 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} /> </div> <div> <TextField - label='Label' - name='label' - value={property.label} + inputRef={this.references.code} + label='Code' + name='code' + value={property.code} fullWidth={true} margin='normal' variant='filled' @@ -136,11 +193,12 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} /> </div> <div> <TextField - multiline + inputRef={this.references.description} label='Description' name='description' value={property.description} @@ -151,11 +209,13 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} /> </div> <div> <TextField select + inputRef={this.references.dataType} label='Data Type' name='dataType' SelectProps={{ @@ -169,6 +229,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} > {dto.DataType.values.sort().map(dataType => { return ( @@ -196,6 +257,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} > <option key='' value=''></option> {vocabularies && @@ -226,6 +288,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { shrink: true }} onChange={this.handleChange} + onFocus={this.handleFocus} > <option key='' value=''></option> {materialTypes && @@ -243,9 +306,14 @@ class ObjectTypeParametersProperty extends React.PureComponent { <FormControlLabel control={ <Checkbox + inputRef={this.references.mandatory} + action={actions => { + this.actions.mandatory = actions + }} value='mandatory' checked={property.mandatory} onChange={this.handleChange} + onFocus={this.handleFocus} /> } label='Mandatory' @@ -258,6 +326,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { value='visible' checked={property.visible} onChange={this.handleChange} + onFocus={this.handleFocus} /> } label='Visible' diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx index 3074fd320adf38b5902f9e5610dcf2d2bdcae4a2..17a63a5b1eba9bc1e6b4428f6baf368ba37b7196 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreview.jsx @@ -10,10 +10,13 @@ import logger from '../../../common/logger.js' const styles = theme => ({ container: { - padding: theme.spacing(2), - height: '100%', - boxSizing: 'border-box' + display: 'flex', + padding: `${theme.spacing(2)}px ${theme.spacing(4)}px` + }, + header: { + marginBottom: theme.spacing(2) }, + form: {}, droppable: { display: 'flex', flexDirection: 'column', @@ -25,11 +28,23 @@ const styles = theme => ({ class ObjectTypePreview extends React.PureComponent { constructor(props) { super(props) + this.state = {} + this.handleDragStart = this.handleDragStart.bind(this) this.handleDragEnd = this.handleDragEnd.bind(this) this.handleClick = this.handleClick.bind(this) } + handleDragStart(start) { + this.setState({ dragging: true }) + + this.props.onSelectionChange(start.type, { + id: start.draggableId + }) + } + handleDragEnd(result) { + this.setState({ dragging: false }) + if (!result.destination) { return } @@ -50,7 +65,9 @@ class ObjectTypePreview extends React.PureComponent { } handleClick() { - this.props.onSelectionChange() + if (!this.state.dragging) { + this.props.onSelectionChange() + } } render() { @@ -60,24 +77,31 @@ class ObjectTypePreview extends React.PureComponent { return ( <div className={classes.container} onClick={this.handleClick}> - <Typography variant='h6'>Form Preview</Typography> - <ObjectTypePreviewCode type={type} /> - <DragDropContext onDragEnd={this.handleDragEnd}> - <Droppable droppableId='root' type='section'> - {provided => ( - <div - ref={provided.innerRef} - {...provided.droppableProps} - className={classes.droppable} - > - {sections.map((section, index) => - this.renderSection(section, index) - )} - {provided.placeholder} - </div> - )} - </Droppable> - </DragDropContext> + <div className={classes.form}> + <Typography variant='h6' className={classes.header}> + Form Preview + </Typography> + <ObjectTypePreviewCode type={type} /> + <DragDropContext + onDragStart={this.handleDragStart} + onDragEnd={this.handleDragEnd} + > + <Droppable droppableId='root' type='section'> + {provided => ( + <div + ref={provided.innerRef} + {...provided.droppableProps} + className={classes.droppable} + > + {sections.map((section, index) => + this.renderSection(section, index) + )} + {provided.placeholder} + </div> + )} + </Droppable> + </DragDropContext> + </div> </div> ) } diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx index 1267a39f7a7d9de050f6e1bc6967c091581addbf..77b78c0684f8ec69dbcf6c43ca52f4edc14f689b 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewCode.jsx @@ -1,6 +1,6 @@ import _ from 'lodash' import React from 'react' -import TextField from '@material-ui/core/TextField' +import TextField from '../../common/form/TextField.jsx' import { withStyles } from '@material-ui/core/styles' import logger from '../../../common/logger.js' @@ -19,7 +19,6 @@ class ObjectTypePreviewCode extends React.PureComponent { label='Code' value={this.getValue()} disabled={this.getDisabled()} - variant='filled' /> ) } diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx index 089d1ab88c64765a02637dc56c8f0ab3b6fc3a79..0492c52266ac947824a3bd79508d6d078bc7a0cd 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewProperty.jsx @@ -2,36 +2,160 @@ import _ from 'lodash' import React from 'react' import { Draggable } from 'react-beautiful-dnd' import { withStyles } from '@material-ui/core/styles' -import ObjectTypePreviewPropertyVarchar from './ObjectTypePreviewPropertyVarchar.jsx' -import ObjectTypePreviewPropertyNumber from './ObjectTypePreviewPropertyNumber.jsx' -import ObjectTypePreviewPropertyBoolean from './ObjectTypePreviewPropertyBoolean.jsx' -import ObjectTypePreviewPropertyVocabulary from './ObjectTypePreviewPropertyVocabulary.jsx' -import ObjectTypePreviewPropertyMaterial from './ObjectTypePreviewPropertyMaterial.jsx' +import CheckboxField from '../../common/form/CheckboxField.jsx' +import TextField from '../../common/form/TextField.jsx' +import SelectField from '../../common/form/SelectField.jsx' +import { facade, dto } from '../../../services/openbis.js' import logger from '../../../common/logger.js' import * as util from '../../../common/util.js' -const styles = () => ({ - container: { - padding: '10px' +const styles = theme => ({ + draggable: { + width: '100%', + padding: theme.spacing(2), + boxSizing: 'border-box', + '&:last-child': { + marginBottom: 0 + }, + '&:hover': { + backgroundColor: theme.palette.background.primary + } + }, + selected: { + backgroundColor: theme.palette.background.secondary, + '&:hover': { + backgroundColor: theme.palette.background.secondary + } + }, + hidden: { + opacity: 0.4 + }, + partEmpty: { + fontStyle: 'italic', + opacity: 0.7, + color: theme.palette.grey.main, + '&:after': { + content: '"empty"' + } }, - selected: {} + partSelected: { + cursor: 'pointer', + paddingBottom: '1px', + borderColor: theme.palette.secondary.main, + borderStyle: 'solid', + borderWidth: '0px 0px 2px 0px', + '&:hover': { + borderColor: theme.palette.secondary.main + } + }, + partNotSelected: { + cursor: 'pointer', + paddingBottom: '1px', + '&:hover': { + borderStyle: 'solid', + borderWidth: '0px 0px 2px 0px', + borderColor: theme.palette.primary.main + } + } }) class ObjectTypePreviewProperty extends React.PureComponent { constructor(props) { super(props) - this.handleClick = this.handleClick.bind(this) + this.state = {} + this.handleDraggableClick = this.handleDraggableClick.bind(this) + this.handlePropertyClick = this.handlePropertyClick.bind(this) } - handleClick(event) { + componentDidMount() { + const { dataType } = this.props.property + + if (dataType === 'MATERIAL') { + this.loadMaterialProperty() + } else if (dataType === 'CONTROLLEDVOCABULARY') { + this.loadVocabularyProperty() + } + } + + componentDidUpdate(prevProps) { + let { property: prevProperty } = prevProps + let { property } = this.props + + if (property.materialType !== prevProperty.materialType) { + this.loadMaterialProperty() + } else if (property.vocabulary !== prevProperty.vocabulary) { + this.loadVocabularyProperty() + } + } + + loadMaterialProperty() { + const materialType = this.props.property.materialType + + if (materialType) { + let criteria = new dto.MaterialSearchCriteria() + let fo = new dto.MaterialFetchOptions() + + criteria + .withType() + .withCode() + .thatEquals(materialType) + + return facade.searchMaterials(criteria, fo).then(result => { + this.setState(() => ({ + materials: result.objects + })) + }) + } else { + this.setState(() => ({ + materials: null + })) + } + } + + loadVocabularyProperty() { + const vocabulary = this.props.property.vocabulary + + if (vocabulary) { + let criteria = new dto.VocabularyTermSearchCriteria() + let fo = new dto.VocabularyTermFetchOptions() + + criteria + .withVocabulary() + .withCode() + .thatEquals(vocabulary) + + return facade.searchVocabularyTerms(criteria, fo).then(result => { + this.setState(() => ({ + terms: result.objects + })) + }) + } else { + this.setState(() => ({ + terms: null + })) + } + } + + handleDraggableClick(event) { + event.stopPropagation() + this.props.onSelectionChange('property', { + id: this.props.property.id, + part: 'label' + }) + } + + handlePropertyClick(event) { event.stopPropagation() - this.props.onSelectionChange('property', { id: this.props.property.id }) + this.props.onSelectionChange('property', { + id: this.props.property.id, + part: event.target.dataset.part + }) } render() { logger.log(logger.DEBUG, 'ObjectTypePreviewProperty.render') - let { property, index, selection, classes } = this.props + let { property, selection, index, classes } = this.props const selected = selection && @@ -46,35 +170,199 @@ class ObjectTypePreviewProperty extends React.PureComponent { {...provided.draggableProps} {...provided.dragHandleProps} className={util.classNames( - classes.container, + classes.draggable, selected ? classes.selected : null )} - onClick={this.handleClick} + onClick={this.handleDraggableClick} > - {this.renderProperty(property)} + {this.renderProperty()} </div> )} </Draggable> ) } - renderProperty(property) { - const dataType = property.dataType + renderProperty() { + const { dataType } = this.props.property if (dataType === 'VARCHAR' || dataType === 'MULTILINE_VARCHAR') { - return <ObjectTypePreviewPropertyVarchar property={property} /> + return this.renderVarcharProperty() } else if (dataType === 'REAL' || dataType === 'INTEGER') { - return <ObjectTypePreviewPropertyNumber property={property} /> + return this.renderNumberProperty() } else if (dataType === 'BOOLEAN') { - return <ObjectTypePreviewPropertyBoolean property={property} /> + return this.renderBooleanProperty() } else if (dataType === 'CONTROLLEDVOCABULARY') { - return <ObjectTypePreviewPropertyVocabulary property={property} /> + return this.renderVocabularyProperty() } else if (dataType === 'MATERIAL') { - return <ObjectTypePreviewPropertyMaterial property={property} /> + return this.renderMaterialProperty() } else { return <span>Data type not supported yet</span> } } + + renderMetadata() { + const { code, dataType } = this.props.property + const styles = this.createStyles() + + return ( + <React.Fragment> + [ + <span + data-part='code' + className={styles.code} + onClick={this.handlePropertyClick} + > + {code} + </span> + ][ + <span + data-part='dataType' + className={styles.dataType} + onClick={this.handlePropertyClick} + > + {dataType} + </span> + ] + </React.Fragment> + ) + } + + renderVarcharProperty() { + const { property } = this.props + return ( + <TextField + label={property.label} + description={property.description} + mandatory={property.mandatory} + metadata={this.renderMetadata()} + styles={this.createStyles()} + onClick={this.handlePropertyClick} + /> + ) + } + + renderNumberProperty() { + const { property } = this.props + return ( + <TextField + type='number' + label={property.label} + description={property.description} + mandatory={property.mandatory} + metadata={this.renderMetadata()} + styles={this.createStyles()} + onClick={this.handlePropertyClick} + /> + ) + } + + renderBooleanProperty() { + const { property } = this.props + return ( + <div> + <CheckboxField + label={property.label} + description={property.description} + mandatory={property.mandatory} + metadata={this.renderMetadata()} + styles={this.createStyles()} + onClick={this.handlePropertyClick} + /> + </div> + ) + } + + renderVocabularyProperty() { + const { property } = this.props + const { terms } = this.state + + let options = [] + if (terms) { + options = terms.map(term => ({ + value: term.code, + label: term.label + })) + } + + return ( + <SelectField + label={property.label} + description={property.description} + mandatory={property.mandatory} + options={options} + metadata={this.renderMetadata()} + styles={this.createStyles()} + onClick={this.handlePropertyClick} + /> + ) + } + + renderMaterialProperty() { + const { property } = this.props + const { materials } = this.state + + let options = [] + if (materials) { + options = materials.map(material => ({ + value: material.code + })) + } + + return ( + <SelectField + label={property.label} + description={property.description} + mandatory={property.mandatory} + options={options} + metadata={this.renderMetadata()} + styles={this.createStyles()} + onClick={this.handlePropertyClick} + /> + ) + } + + createStyles() { + const { property, selection, classes } = this.props + + let styles = {} + + const parts = ['code', 'label', 'dataType', 'mandatory', 'description'] + const selectedPart = + selection && + selection.type === 'property' && + selection.params.id === property.id && + selection.params.part + + parts.forEach(part => { + const partStyles = [] + + if (part === selectedPart) { + partStyles.push(classes.partSelected) + } else { + partStyles.push(classes.partNotSelected) + } + + const partValue = property[part] + + if (!partValue) { + partStyles.push(classes.partEmpty) + } + + styles = { + ...styles, + [part]: partStyles.join(' ') + } + }) + + if (!property.visible) { + styles = { + ...styles, + container: classes.hidden + } + } + + return styles + } } export default _.flow(withStyles(styles))(ObjectTypePreviewProperty) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx deleted file mode 100644 index b377b87d11cc6abcfaf0577648227748a30a9b46..0000000000000000000000000000000000000000 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyBoolean.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import FormControl from '@material-ui/core/FormControl' -import FormControlLabel from '@material-ui/core/FormControlLabel' -import FormHelperText from '@material-ui/core/FormHelperText' -import Checkbox from '@material-ui/core/Checkbox' -import { withStyles } from '@material-ui/core/styles' -import logger from '../../../common/logger.js' - -const styles = () => ({ - visible: { - opacity: 1 - }, - hidden: { - opacity: 0.5 - } -}) - -class ObjectTypePreviewPropertyBoolean extends React.PureComponent { - constructor(props) { - super(props) - } - - render() { - logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyBoolean.render') - - const { property, classes } = this.props - - return ( - <FormControl - className={property.visible ? classes.visible : classes.hidden} - > - <FormControlLabel control={<Checkbox />} label={property.label} /> - <FormHelperText>{property.description}</FormHelperText> - </FormControl> - ) - } -} - -export default withStyles(styles)(ObjectTypePreviewPropertyBoolean) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx deleted file mode 100644 index 5ed0e1ef494bf28ab20ccf6619b226730ae456e8..0000000000000000000000000000000000000000 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyMaterial.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react' -import TextField from '@material-ui/core/TextField' -import { withStyles } from '@material-ui/core/styles' -import { facade, dto } from '../../../services/openbis.js' -import logger from '../../../common/logger.js' - -const styles = () => ({ - visible: { - opacity: 1 - }, - hidden: { - opacity: 0.5 - } -}) - -class ObjectTypePreviewPropertyMaterial extends React.PureComponent { - constructor(props) { - super(props) - this.state = {} - } - - componentDidMount() { - this.load() - } - - componentDidUpdate(prevProps) { - if (this.props.property.materialType !== prevProps.property.materialType) { - this.load() - } - } - - load() { - if (this.props.property.materialType) { - let criteria = new dto.MaterialSearchCriteria() - let fo = new dto.MaterialFetchOptions() - - criteria - .withType() - .withCode() - .thatEquals(this.props.property.materialType) - - return facade.searchMaterials(criteria, fo).then(result => { - this.setState(() => ({ - materials: result.objects - })) - }) - } else { - this.setState(() => ({ - materials: null - })) - } - } - - render() { - logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyMaterial.render') - - const { property, classes } = this.props - const { materials } = this.state - - return ( - <TextField - select - error={property.mandatory} - SelectProps={{ - native: true - }} - InputLabelProps={{ - shrink: true - }} - label={property.label} - helperText={property.description} - fullWidth={true} - className={property.visible ? classes.visible : classes.hidden} - variant='filled' - > - {materials && - materials.map(material => ( - <option key={material.code} value={material.code}> - {material.code} - </option> - ))} - <React.Fragment></React.Fragment> - </TextField> - ) - } -} - -export default withStyles(styles)(ObjectTypePreviewPropertyMaterial) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx deleted file mode 100644 index 659cba4322b41db81266ca05a6f75ea8aa7a3b8c..0000000000000000000000000000000000000000 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyNumber.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import TextField from '@material-ui/core/TextField' -import { withStyles } from '@material-ui/core/styles' -import logger from '../../../common/logger.js' - -const styles = () => ({ - visible: { - opacity: 1 - }, - hidden: { - opacity: 0.5 - } -}) - -class ObjectTypePreviewPropertyNumber extends React.PureComponent { - constructor(props) { - super(props) - } - - render() { - logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyNumber.render') - - const { property, classes } = this.props - - return ( - <TextField - error={property.mandatory} - type='number' - label={property.label} - helperText={property.description} - fullWidth={true} - InputLabelProps={{ - shrink: true - }} - className={property.visible ? classes.visible : classes.hidden} - variant='filled' - /> - ) - } -} - -export default withStyles(styles)(ObjectTypePreviewPropertyNumber) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx deleted file mode 100644 index f60d83024b1c28b2d7ce36d9a7464a71e103c278..0000000000000000000000000000000000000000 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVarchar.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import TextField from '@material-ui/core/TextField' -import { withStyles } from '@material-ui/core/styles' -import logger from '../../../common/logger.js' - -const styles = () => ({ - visible: { - opacity: 1 - }, - hidden: { - opacity: 0.5 - } -}) - -class ObjectTypePreviewPropertyVarchar extends React.PureComponent { - constructor(props) { - super(props) - } - - render() { - logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyVarchar.render') - - const { property, classes } = this.props - - return ( - <TextField - error={property.mandatory} - multiline={property.dataType === 'MULTILINE_VARCHAR'} - label={property.label} - helperText={property.description} - fullWidth={true} - InputLabelProps={{ - shrink: true - }} - className={property.visible ? classes.visible : classes.hidden} - variant='filled' - /> - ) - } -} - -export default withStyles(styles)(ObjectTypePreviewPropertyVarchar) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx deleted file mode 100644 index 5f45d3b4a7a2e68162c727dea44a2010237cfd01..0000000000000000000000000000000000000000 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewPropertyVocabulary.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react' -import TextField from '@material-ui/core/TextField' -import { withStyles } from '@material-ui/core/styles' -import { facade, dto } from '../../../services/openbis.js' -import logger from '../../../common/logger.js' - -const styles = () => ({ - visible: { - opacity: 1 - }, - hidden: { - opacity: 0.5 - } -}) - -class ObjectTypePreviewPropertyVocabulary extends React.PureComponent { - constructor(props) { - super(props) - this.state = {} - } - - componentDidMount() { - this.load() - } - - componentDidUpdate(prevProps) { - if (this.props.property.vocabulary !== prevProps.property.vocabulary) { - this.load() - } - } - - load() { - if (this.props.property.vocabulary) { - let criteria = new dto.VocabularyTermSearchCriteria() - let fo = new dto.VocabularyTermFetchOptions() - - criteria - .withVocabulary() - .withCode() - .thatEquals(this.props.property.vocabulary) - - return facade.searchVocabularyTerms(criteria, fo).then(result => { - this.setState(() => ({ - terms: result.objects - })) - }) - } else { - this.setState(() => ({ - terms: null - })) - } - } - - render() { - logger.log(logger.DEBUG, 'ObjectTypePreviewPropertyVocabulary.render') - - const { property, classes } = this.props - const { terms } = this.state - - return ( - <TextField - select - error={property.mandatory} - SelectProps={{ - native: true - }} - InputLabelProps={{ - shrink: true - }} - label={property.label} - helperText={property.description} - fullWidth={true} - className={property.visible ? classes.visible : classes.hidden} - variant='filled' - > - {terms && - terms.map(term => ( - <option key={term.code} value={term.code}> - {term.label || term.code} - </option> - ))} - <React.Fragment></React.Fragment> - </TextField> - ) - } -} - -export default withStyles(styles)(ObjectTypePreviewPropertyVocabulary) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx index 6afa3c11bbb1d0c02ce5e0be3cf41607e3af84e8..d84ead6d773cd610a3745a330b53a178a465a1b6 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypePreviewSection.jsx @@ -2,15 +2,41 @@ import _ from 'lodash' import React from 'react' import { Draggable, Droppable } from 'react-beautiful-dnd' import { withStyles } from '@material-ui/core/styles' +import Typography from '@material-ui/core/Typography' import logger from '../../../common/logger.js' import * as util from '../../../common/util.js' -const styles = () => ({ - container: { - padding: '10px' +const styles = theme => ({ + draggable: { + width: '100%', + marginBottom: theme.spacing(2), + backgroundColor: theme.palette.background.paper + }, + droppable: { + padding: theme.spacing(2), + borderWidth: '2px', + borderStyle: 'dashed', + borderColor: theme.palette.background.secondary, + '&:hover': { + borderColor: theme.palette.primary.main + } + }, + named: { + '& $droppable': { + borderStyle: 'solid', + borderColor: theme.palette.background.secondary, + '&:hover': { + borderColor: theme.palette.primary.main + } + } }, selected: { - border: '1px solid red' + '& $droppable': { + borderColor: theme.palette.secondary.main, + '&:hover': { + borderColor: theme.palette.secondary.main + } + } } }) @@ -44,18 +70,25 @@ class ObjectTypePreviewSection extends React.PureComponent { {...provided.draggableProps} {...provided.dragHandleProps} className={util.classNames( - classes.container, + classes.draggable, + name ? classes.named : null, selected ? classes.selected : null )} onClick={this.handleClick} > <Droppable droppableId={id} type='property'> {provided => ( - <div ref={provided.innerRef} {...provided.droppableProps}> - Section {name} - <div>{children}</div> - {provided.placeholder} - </div> + <React.Fragment> + <Typography variant='h6'>{name}</Typography> + <div + ref={provided.innerRef} + {...provided.droppableProps} + className={classes.droppable} + > + <div>{children}</div> + {provided.placeholder} + </div> + </React.Fragment> )} </Droppable> </div> diff --git a/openbis_ng_ui/src/js/index.js b/openbis_ng_ui/src/js/index.js index 031638523f99265a87d5d7fd23def49fcdcc60eb..92361b43b0f2483ec9e290d159046349cc12f0fd 100644 --- a/openbis_ng_ui/src/js/index.js +++ b/openbis_ng_ui/src/js/index.js @@ -12,6 +12,9 @@ const theme = createMuiTheme({ useNextVariants: true }, palette: { + grey: { + main: '#5d5d5d' + }, primary: { main: indigo[700] }, diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py index 4cc88556994c50450e82e53735b63caa07957f2f..1406bea586a53b982e4b516ad3f67b8c636550ca 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/initialize-master-data.py @@ -23,7 +23,7 @@ import sys from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNCollectionTypeMigration -ELNCollectionTypeMigration.migrate() +ELNCollectionTypeMigration.beforeUpgrade() helper = MasterDataRegistrationHelper(sys.path) api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME) @@ -32,6 +32,9 @@ props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByt .withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\ .withParameter('scripts', helper.getAllScripts()) result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import-api"), props) + +ELNCollectionTypeMigration.afterUpgrade() + print("======================== master-data xls ingestion result ========================") print(result) print("======================== master-data xls ingestion result ========================") diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql index ebfaa1ee424c1d2a8e5a982df2e3d2e9d57e0631..ef028f0304e120602e48296337b83d1c5e451b48 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/custom_widget_migration.sql @@ -1 +1,2 @@ -UPDATE property_types SET meta_data = '{ "custom_widget" : "Word Processor" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR')); \ No newline at end of file +UPDATE property_types SET meta_data = '{ "custom_widget" : "Word Processor" }'::jsonb WHERE id IN (SELECT id FROM property_types WHERE daty_id = (SELECT id FROM data_types WHERE code = 'MULTILINE_VARCHAR')); +UPDATE property_types SET meta_data = NULL WHERE id IN (SELECT id FROM property_types WHERE daty_id IN (SELECT id FROM data_types WHERE code NOT IN ('MULTILINE_VARCHAR', 'XML'))); \ No newline at end of file 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 09f5c1d3db11b44eb21a8523d9c963ab6a710db2..01598226b0d7d30ba7567a7642944efa899596ed 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/etc/config.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/etc/config.js index 6b8215d9ea593e26998f7914982b68021cbbfaf6..d9bf2469a0f2e7fa3c2ef9c940dac515a0052bb5 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 @@ -27,4 +27,8 @@ var onLoadInstanceProfileResorceFunc = function() { //<PROFILE_PLACEHOLDER> loadJSResorce("./etc/InstanceProfile.js", onLoadInstanceProfileResorceFunc); -//</PROFILE_PLACEHOLDER> \ No newline at end of file +//</PROFILE_PLACEHOLDER> + +var PLUGINS_CONFIGURATION = { + extraPlugins : ["life-sciences", "flow", "microscopy"] +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html index d13cc2116314d23f6350d2ef18492c97e09547bf..24f2a40f3579c2acc493164265d491d3797270cc 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html @@ -283,13 +283,13 @@ <script type="text/javascript" src="./js/views/ExperimentTable/ExperimentTableModel.js"></script> <script type="text/javascript" src="./js/views/ExperimentTable/ExperimentTableView.js"></script> - <script type="text/javascript" src="./js/plugins/ELNLIMSPlugin.js"></script> - <script type="text/javascript" src="./js/plugins/GenericTechnology.js"></script> - <script type="text/javascript" src="./js/plugins/LifeSciencesTechnology.js"></script> - <script type="text/javascript" src="./js/plugins/MicroscopyTechnology.js"></script> - <script type="text/javascript" src="./js/plugins/FlowCytometryTechnology.js"></script> + <script type="text/javascript" src="./js/config/ELNLIMSPlugin.js"></script> + <script type="text/javascript" src="./plugins/generic/plugin.js"></script> <script type="text/javascript" src="./js/util/EventUtil.js"></script> + <script type="text/javascript" src="./js/test/TestUtil.js"></script> + <script type="text/javascript" src="./js/test/UserTests.js"></script> + <script type="text/javascript" src="./js/test/AdminTests.js"></script> <script type="text/javascript" src="./js/test/TestProtocol.js"></script> <script type="text/javascript"> @@ -373,22 +373,36 @@ } var test = queryString.test; if(test == "true") { - TestProtocol.startAdmitTests(); + TestProtocol.startAdminTests(); } if ($.cookie("suitename") == "testId") { - TestProtocol.startTestUserTests(); + TestProtocol.startUserTests(); } } - + $(document).ready(function() { + var numPlugins = !PLUGINS_CONFIGURATION?1:1+PLUGINS_CONFIGURATION.extraPlugins.length; + var pluginsOnLoad = false; + var wait = null; wait = function() { - var condition = profile !== null; - if (!condition){ + if (!profile) { setTimeout(wait,100); } else { - startELNLIMS(); + if(profile.plugins.length === numPlugins) { + startELNLIMS(); + } else if(!pluginsOnLoad) { + if(PLUGINS_CONFIGURATION && PLUGINS_CONFIGURATION.extraPlugins) { + for(var pIdx = 0; pIdx < PLUGINS_CONFIGURATION.extraPlugins.length; pIdx++) { + loadJSResorce("./plugins/" + PLUGINS_CONFIGURATION.extraPlugins[pIdx] + "/plugin.js"); + } + } + pluginsOnLoad = true; + setTimeout(wait,100); + } else { + setTimeout(wait,100); + } } } wait(); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/ELNLIMSPlugin.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/ELNLIMSPlugin.js similarity index 100% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/ELNLIMSPlugin.js rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/ELNLIMSPlugin.js 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 ae4abd2c14c6877d61a9d2629ced5438dfc07f02..bc6f0493ec6a7faa51755f00738c06a88ac1b85f 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 @@ -140,7 +140,8 @@ $.extend(DefaultProfile.prototype, { this.customWidgetSettings = {}; - this.plugins = [new GenericTechnology(), new LifeSciencesTechnology(), new MicroscopyTechnology(), new FlowCytometryTechnology()]; + this.plugins = [new GenericTechnology()]; + this.sampleFormTop = function($container, model) { for(var i = 0; i < this.plugins.length; i++) { this.plugins[i].sampleFormTop($container, model); @@ -397,7 +398,7 @@ $.extend(DefaultProfile.prototype, { "experimentTypeCodes" : [] } this.hideTypes = { - "sampleTypeCodes" : ["GENERAL_ELN_SETTINGS", "STORAGE_POSITION", "STORAGE"], + "sampleTypeCodes" : [], "experimentTypeCodes" : [] } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js index 4445b07a03fffc80f9fceca08c12ee706042fbde..fee91797c66186edb6d22a379a65e6bb450ccbee 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/config/SettingsManager.js @@ -121,8 +121,14 @@ function SettingsManager(serverFacade) { for (var sampleTypeCode of Object.keys(settings.sampleTypeDefinitionsExtension)) { // sampleTypeDefinitionsExtension gets overwritten with settings if found - targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode] = settings.sampleTypeDefinitionsExtension[sampleTypeCode]; - + if(!targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode]) { + targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode] = {}; + } + + for(var key in settings.sampleTypeDefinitionsExtension[sampleTypeCode]) { // Key by Key, in case there is new Keys not available in the old config + targetProfile.sampleTypeDefinitionsExtension[sampleTypeCode][key] = settings.sampleTypeDefinitionsExtension[sampleTypeCode][key]; + } + // Remove current profile show config if($.inArray(sampleTypeCode, targetProfile.hideTypes["sampleTypeCodes"]) !== -1) { var indexToRemove = $.inArray(sampleTypeCode, targetProfile.hideTypes["sampleTypeCodes"]); 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 cf273afe3bfe7883c1fbe5f6e7775c43a7243742..58d16349d6756ca277b1e2e39887d32da9d2dc19 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 @@ -370,7 +370,7 @@ function ServerFacade(openbisServer) { // // Research collection export // - this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, userInformation, callbackFunction) { + this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, retentionPeriod, userInformation, callbackFunction) { this.asyncExportRc({ "method": "exportAll", "includeRoot": includeRoot, @@ -378,6 +378,7 @@ function ServerFacade(openbisServer) { "metadataOnly": metadataOnly, "submissionUrl": submissionUrl, "submissionType": submissionType, + "retentionPeriod": retentionPeriod, "userInformation": userInformation, "originUrl": window.location.origin, "sessionToken": this.openbisServer.getSession(), @@ -403,6 +404,7 @@ function ServerFacade(openbisServer) { options.withParameter("method", parameters["method"]); options.withParameter("originUrl", parameters["originUrl"]); options.withParameter("submissionType", parameters["submissionType"]); + options.withParameter("retentionPeriod", parameters["retentionPeriod"]); options.withParameter("submissionUrl", parameters["submissionUrl"]); options.withParameter("entities", parameters["entities"]); options.withParameter("userId", parameters["userInformation"]["id"]); @@ -473,6 +475,15 @@ function ServerFacade(openbisServer) { "method": "getSubmissionTypes", }, callbackFunction, "rc-exports-api"); }; + + // + // Gets submission types + // + this.listSubmissionTypes = function(callbackFunction) { + this.customELNApi({ + "method" : "getSubmissionTypes", + }, callbackFunction, "rc-exports-api"); + }; // // Metadata Related Functions @@ -2406,6 +2417,15 @@ function ServerFacade(openbisServer) { // V3 Functions // + this.createPermIdStrings = function(count, callbackFunction) { + mainController.openbisV3.createPermIdStrings(count) + .done(function(result) { + callbackFunction(result); + }).fail(function(result) { + Util.showFailedServerCallError(result); + }); + } + this.getSpace = function(spaceIdentifier, callbackFunction) { require(["as/dto/space/id/SpacePermId", "as/dto/space/fetchoptions/SpaceFetchOptions"], function(SpacePermId, SpaceFetchOptions) { diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js new file mode 100644 index 0000000000000000000000000000000000000000..b9ef1e42f4615e02436a204b9185fbdc3392ff46 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/AdminTests.js @@ -0,0 +1,137 @@ +var AdminTests = new function() { + + this.startAdminTests = function() { + testChain = Promise.resolve(); + //1. Login + testChain.then(() => TestUtil.login("admin", "a")) + //2. Inventory Space and Sample Types + .then(() => this.inventorySpace()) + //3. Settings Form - Enable Sample Types to Show in Drop-downs + .then(() => this.enableBacteriaToShowInDropDowns()) + //5. User Manager + .then(() => this.userManager()) + .catch(error => { console.log(error) }); + } + + this.inventorySpace = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + var ids = ["tree", + "LAB_NOTEBOOK", + "INVENTORY", + "MATERIALS", + "_MATERIALS_BACTERIA_BACTERIA_COLLECTION", + "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION", + "_MATERIALS_FLIES_FLY_COLLECTION", + "_MATERIALS_PLANTS_PLANT_COLLECTION", + "_MATERIALS_PLASMIDS_PLASMID_COLLECTION", + "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION", + "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION", + "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION", + "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION", + "_MATERIALS_REAGENTS_ENZYME_COLLECTION", + "_MATERIALS_REAGENTS_MEDIA_COLLECTION", + "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION", + "_MATERIALS_YEASTS_YEAST_COLLECTION", + "METHODS", + "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS", + "_METHODS_PROTOCOLS_PCR_PROTOCOLS", + "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS", + "PUBLICATIONS", + "_PUBLICATIONS_PUBLIC_REPOSITORIES_PUBLICATIONS_COLLECTION", + "STOCK", + "USER_PROFILE", + "SAMPLE_BROWSER", + "VOCABULARY_BROWSER", + "ADVANCED_SEARCH", + "STORAGE_MANAGER", + "USER_MANAGER", + "TRASHCAN", + "SETTINGS"]; + + Promise.resolve().then(() => TestUtil.verifyInventory(ids)).then(() => resolve()); + }); + } + + this.enableBacteriaToShowInDropDowns = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + testChain = Promise.resolve(); + testChain.then(() => e.waitForId("SETTINGS")) + .then(() => e.click("SETTINGS")) + .then(() => e.waitForId("settingsDropdown")) + .then(() => e.change("settingsDropdown", "/ELN_SETTINGS/GENERAL_ELN_SETTINGS")) + .then(() => e.waitForId("edit-btn")) + .then(() => e.click("edit-btn")) + // we wait for the save-button, cause page contains settings-section-sample type-BACTERIA + // even when page can't be edit. So we wait when page be reloaded. + .then(() => e.waitForId("save-btn")) + .then(() => e.click("settings-section-sampletype-BACTERIA")) + .then(() => e.waitForId("BACTERIA_show_in_drop_downs")) + .then(() => e.checked("BACTERIA_show_in_drop_downs", true)) + .then(() => e.click("save-btn")) + // wait until the save + .then(() => e.waitForId("edit-btn")) + .then(() => resolve()) + .catch(() => reject(error)); + }); + } + + this.userManager = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + testChain = Promise.resolve(); + testChain.then(() => e.waitForId("USER_MANAGER")) + .then(() => e.click("USER_MANAGER")) + .then(() => e.waitForId("optionsMenu")) + .then(() => e.click("optionsMenu")) + .then(() => e.waitForId("createUser")) + .then(() => e.click("createUser")) + .then(() => e.waitForId("userId")) + .then(() => e.change("userId", "testId")) + .then(() => e.click("createUserBtn")) + .then(() => AdminTests.createPassword()) + .then(() => AdminTests.userExist()) + .then(() => TestUtil.setCookies("suitename", "testId")) + .then(() => e.click("logoutBtn")) + .then(() => resolve()) + .catch(() => reject(error)); + }); + } + + // Sometimes it ask for password. Try to fill it. + this.createPassword = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + testChain = Promise.resolve(); + + testChain.then(() => e.waitForId("passwordId", true, 2000)) + .then(() => e.change("passwordId", "pass", true)) + .then(() => e.change("passwordRepeatId", "pass", true)) + .then(() => e.click("createUserBtn", true)) + .then(() => resolve()) + .catch(() => reject(error)); + }); + } + + // If the user already exists, we will see an error. + // This is not a problem for the script, and we can continue. + this.userExist = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + testChain = Promise.resolve(); + + testChain.then(() => e.waitForId("jError", true, 2000)) + .then(() => e.waitForId("jNotifyDismiss", true, 2000)) + .then(() => e.click("jNotifyDismiss", true)) + .then(() => e.click("cancelBtn", true)) + .then(() => resolve()) + .catch(() => reject(error)); + }); + } +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js index e47c967cb5b47da628eb24ecb02c0eab109546f9..378bdd6848e5a6a05cb905f78a5d7a0329ca309f 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestProtocol.js @@ -1,211 +1,14 @@ var TestProtocol = new function () { - this.startAdmitTests = function() { + this.startAdminTests = function() { testChain = Promise.resolve(); - testChain.then(() => this.login("admin", "a")) - .then(() => this.inventorySpace()) - .then(() => this.enableBacteriaToShowInDropDowns()) - .then(() => this.userManager()) + testChain.then(() => AdminTests.startAdminTests()) .catch(error => { console.log(error) }); } - this.startTestUserTests = function() { + this.startUserTests = function() { testChain = Promise.resolve(); - testChain.then(() => this.deleteCookies("suitename")) - .then(() => this.login("testId", "pass")) - .then(() => this.inventorySpaceForTestUser()) + testChain.then(() => UserTests.startUserTests()) .catch(error => { console.log(error) }); } - - this.login = function(username, password) { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - testChain = Promise.resolve(); - testChain.then(() => e.waitForId("username")) - .then(() => e.write("username", username)) - .then(() => e.write("password", password)) - .then(() => e.click("login-button")) - .then(() => resolve()) - .catch(() => reject(error)); - }); - } - - this.inventorySpace = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - var ids = ["tree", - "LAB_NOTEBOOK", - "INVENTORY", - "MATERIALS", - "_MATERIALS_BACTERIA_BACTERIA_COLLECTION", - "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION", - "_MATERIALS_FLIES_FLY_COLLECTION", - "_MATERIALS_PLANTS_PLANT_COLLECTION", - "_MATERIALS_PLASMIDS_PLASMID_COLLECTION", - "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION", - "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION", - "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION", - "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION", - "_MATERIALS_REAGENTS_ENZYME_COLLECTION", - "_MATERIALS_REAGENTS_MEDIA_COLLECTION", - "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION", - "_MATERIALS_YEASTS_YEAST_COLLECTION", - "METHODS", - "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS", - "_METHODS_PROTOCOLS_PCR_PROTOCOLS", - "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS", - "PUBLICATIONS", - "_PUBLICATIONS_PUBLIC_REPOSITORIES_PUBLICATIONS_COLLECTION", - "STOCK", - "USER_PROFILE", - "SAMPLE_BROWSER", - "VOCABULARY_BROWSER", - "ADVANCED_SEARCH", - "STORAGE_MANAGER", - "USER_MANAGER", - "TRASHCAN", - "SETTINGS"]; - - Promise.resolve().then(() => TestProtocol.verifyInventory(ids)).then(() => resolve()); - }); - } - - this.enableBacteriaToShowInDropDowns = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - testChain = Promise.resolve(); - testChain.then(() => e.waitForId("SETTINGS")) - .then(() => e.click("SETTINGS")) - .then(() => e.waitForId("settingsDropdown")) - .then(() => e.change("settingsDropdown", "/ELN_SETTINGS/GENERAL_ELN_SETTINGS")) - .then(() => e.waitForId("edit-btn")) - .then(() => e.click("edit-btn")) - // we wait for the save-button, cause page contains settings-section-sample type-BACTERIA - // even when page can't be edit. So we wait when page be reloaded. - .then(() => e.waitForId("save-btn")) - .then(() => e.click("settings-section-sampletype-BACTERIA")) - .then(() => e.waitForId("BACTERIA_show_in_drop_downs")) - .then(() => e.checked("BACTERIA_show_in_drop_downs", true)) - .then(() => e.click("save-btn")) - // wait until the save - .then(() => e.waitForId("edit-btn")) - .then(() => resolve()) - .catch(() => reject(error)); - }); - } - - this.userManager = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - testChain = Promise.resolve(); - testChain.then(() => e.waitForId("USER_MANAGER")) - .then(() => e.click("USER_MANAGER")) - .then(() => e.waitForId("optionsMenu")) - .then(() => e.click("optionsMenu")) - .then(() => e.waitForId("createUser")) - .then(() => e.click("createUser")) - .then(() => e.waitForId("userId")) - .then(() => e.change("userId", "testId")) - .then(() => e.click("createUserBtn")) - .then(() => TestProtocol.createPassword()) - .then(() => TestProtocol.userExist()) - .then(() => TestProtocol.setCookies("suitename", "testId")) - .then(() => e.click("logoutBtn")) - .then(() => resolve()) - .catch(() => reject(error)); - }); - } - - this.inventorySpaceForTestUser = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - var ids = ["LAB_NOTEBOOK", - "TESTID", - "MATERIALS", - "_MATERIALS_BACTERIA_BACTERIA_COLLECTION", - "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION", - "_MATERIALS_FLIES_FLY_COLLECTION", - "_MATERIALS_PLANTS_PLANT_COLLECTION", - "_MATERIALS_PLASMIDS_PLASMID_COLLECTION", - "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION", - "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION", - "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION", - "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION", - "_MATERIALS_REAGENTS_ENZYME_COLLECTION", - "_MATERIALS_REAGENTS_MEDIA_COLLECTION", - "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION", - "_MATERIALS_YEASTS_YEAST_COLLECTION", - "METHODS", - "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS", - "_METHODS_PROTOCOLS_PCR_PROTOCOLS", - "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS"]; - - Promise.resolve().then(() => TestProtocol.verifyInventory(ids)) - .then(() => e.verifyExistence("USER_MANAGER", false)) - .then(() => resolve()); - }); - } - - this.setCookies = function(key, value) { - return new Promise(function executor(resolve, reject) { - $.cookie(key, value); - resolve(); - }); - } - - this.deleteCookies = function(key) { - return new Promise(function executor(resolve, reject) { - $.removeCookie(key); - resolve(); - }); - } - - this.verifyInventory = function(ids) { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - chain = Promise.resolve(); - for (let i = 0; i < ids.length; i++) { - chain = chain.then(() => e.waitForId(ids[i])).catch(error => { reject(error)}); - } - chain.then(() => resolve()); - }); - } - - // Sometimes it ask for password. Try to fill it. - this.createPassword = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - testChain = Promise.resolve(); - - testChain.then(() => e.waitForId("passwordId", true, 2000)) - .then(() => e.change("passwordId", "pass", true)) - .then(() => e.change("passwordRepeatId", "pass", true)) - .then(() => e.click("createUserBtn", true)) - .then(() => resolve()) - .catch(() => reject(error)); - }); - } - - // If the user already exists, we will see an error. - // This is not a problem for the script, and we can continue. - this.userExist = function() { - return new Promise(function executor(resolve, reject) { - var e = EventUtil; - - testChain = Promise.resolve(); - - testChain.then(() => e.waitForId("jError", true, 2000)) - .then(() => e.waitForId("jNotifyDismiss", true, 2000)) - .then(() => e.click("jNotifyDismiss", true)) - .then(() => e.click("cancelBtn", true)) - .then(() => resolve()) - .catch(() => reject(error)); - }); - } } \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js new file mode 100644 index 0000000000000000000000000000000000000000..22d356b17c4252c60b0def1fbb8c06b699c2949b --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/TestUtil.js @@ -0,0 +1,41 @@ +var TestUtil = new function() { + + this.login = function(username, password) { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + testChain = Promise.resolve(); + testChain.then(() => e.waitForId("username")) + .then(() => e.write("username", username)) + .then(() => e.write("password", password)) + .then(() => e.click("login-button")) + .then(() => resolve()) + .catch(() => reject(error)); + }); + } + + this.setCookies = function(key, value) { + return new Promise(function executor(resolve, reject) { + $.cookie(key, value); + resolve(); + }); + } + + this.deleteCookies = function(key) { + return new Promise(function executor(resolve, reject) { + $.removeCookie(key); + resolve(); + }); + } + + this.verifyInventory = function(ids) { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + chain = Promise.resolve(); + for (let i = 0; i < ids.length; i++) { + chain = chain.then(() => e.waitForId(ids[i])).catch(error => { reject(error)}); + } + chain.then(() => resolve()); + }); + } +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js new file mode 100644 index 0000000000000000000000000000000000000000..1a23fac5b3f2122d7925da4b1c359bf73ea3de16 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/test/UserTests.js @@ -0,0 +1,56 @@ +var UserTests = new function() { + + this.startUserTests = function() { + testChain = Promise.resolve(); + //5. User Manager (end of test) + testChain.then(() => TestUtil.deleteCookies("suitename")) + .then(() => TestUtil.login("testId", "pass")) + .then(() => this.inventorySpaceForTestUser()) + //6. Sample Form - Creation + .then(() => this.creationSampleForm()) + .catch(error => { console.log(error) }); + } + + + this.inventorySpaceForTestUser = function() { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + var ids = ["LAB_NOTEBOOK", + "TESTID", + "MATERIALS", + "_MATERIALS_BACTERIA_BACTERIA_COLLECTION", + "_MATERIALS_CELL_LINES_CELL_LINE_COLLECTION", + "_MATERIALS_FLIES_FLY_COLLECTION", + "_MATERIALS_PLANTS_PLANT_COLLECTION", + "_MATERIALS_PLASMIDS_PLASMID_COLLECTION", + "_MATERIALS_POLYNUCLEOTIDES_OLIGO_COLLECTION", + "_MATERIALS_POLYNUCLEOTIDES_RNA_COLLECTION", + "_MATERIALS_REAGENTS_ANTIBODY_COLLECTION", + "_MATERIALS_REAGENTS_CHEMICAL_COLLECTION", + "_MATERIALS_REAGENTS_ENZYME_COLLECTION", + "_MATERIALS_REAGENTS_MEDIA_COLLECTION", + "_MATERIALS_REAGENTS_SOLUTION_BUFFER_COLLECTION", + "_MATERIALS_YEASTS_YEAST_COLLECTION", + "METHODS", + "_METHODS_PROTOCOLS_GENERAL_PROTOCOLS", + "_METHODS_PROTOCOLS_PCR_PROTOCOLS", + "_METHODS_PROTOCOLS_WESTERN_BLOTTING_PROTOCOLS"]; + + Promise.resolve().then(() => TestUtil.verifyInventory(ids)) + .then(() => e.verifyExistence("USER_MANAGER", false)) + .then(() => resolve()); + }); + } + + this.creationSampleForm = function(key, value) { + return new Promise(function executor(resolve, reject) { + var e = EventUtil; + + Promise.resolve().then(() => e.waitForId("_MATERIALS_BACTERIA_BACTERIA_COLLECTION")) + .then(() => e.click("_MATERIALS_BACTERIA_BACTERIA_COLLECTION")) + .then(() => resolve()); + }); + } + +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js index c1cb04b747b1bf9e7d46eb0da22c55e519b639aa..1c588ff7e75307a29b5911656c65606acd738b8d 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/BarcodeUtil.js @@ -1,5 +1,5 @@ var BarcodeUtil = new function() { - + var MIN_BARCODE_LENGTH = 10; var barcodeTimeout = false; var barcodeReader = ""; @@ -8,16 +8,16 @@ var BarcodeUtil = new function() { // permID Format 23 char, 1 hyphen: 20170912112249208-38888 // UUID Format 36 char, 4 hyphens: 123e4567-e89b-12d3-a456-426655440000 var rules = {}; - if(barcodeReader.length === 36) { - rules["UUIDv4"] = { type: "Property/Attribute", name: "PROP.$BARCODE", operator : "thatEqualsString", value: barcodeReader }; - } else if(barcodeReader.length > 17) { - rules["UUIDv4"] = { type: "Property/Attribute", name: "ATTR.PERM_ID", operator : "thatEqualsString", value: barcodeReader }; + + if(barcodeReader.length >= MIN_BARCODE_LENGTH) { + rules["UUIDv4-1"] = { type: "Property/Attribute", name: "PROP.$BARCODE", operator : "thatEqualsString", value: barcodeReader }; + rules["UUIDv4-2"] = { type: "Property/Attribute", name: "ATTR.PERM_ID", operator : "thatEqualsString", value: barcodeReader }; } if(rules) { var criteria = {}; criteria.entityKind = "SAMPLE"; - criteria.logicalOperator = "AND"; + criteria.logicalOperator = "OR"; criteria.rules = rules; mainController.serverFacade.searchForSamplesAdvanced(criteria, { only : true, withProperties: true }, @@ -87,9 +87,11 @@ var BarcodeUtil = new function() { $generateBtn.click(function() { views.content.empty(); var value = parseInt($numberDropdown.val()); - for(var idx = 0; idx < value; idx++) { - _this.addBarcode(views.content, idx, $barcodeTypesDropdown.val()); - } + mainController.serverFacade.createPermIdStrings(value, function(newPermIds) { + for(var idx = 0; idx < value; idx++) { + _this.addBarcode(views.content, idx, $barcodeTypesDropdown.val(), newPermIds[idx], newPermIds[idx]); + } + }); }); this.preloadLibrary(); @@ -104,11 +106,10 @@ var BarcodeUtil = new function() { }); } - this.addBarcode = function(content, idx, type) { - var uuid = Util.guid(); + this.addBarcode = function(content, idx, type, text) { content.append($('<br>')); content.append($('<center>').append($('<canvas>', { id : "barcode-canvas-" + idx, width : 1, height : 1, style : "border:1px solid #fff;visibility:hidden" }))); - this.generateBarcode("barcode-canvas-" + idx, type, uuid, uuid); + this.generateBarcode("barcode-canvas-" + idx, type, text, text); } this.readBarcodeMulti = function(actionLabel, action) { @@ -122,8 +123,19 @@ var BarcodeUtil = new function() { var gatherReaded = function(object) { objects.push(object); var displayName = ""; - $readed.append($('<div>').append(object.identifier.identifier)); + var $container = $('<div>'); + var $identifier = $('<span>').append(object.identifier.identifier); + var $removeBtn = FormUtil.getButtonWithIcon("glyphicon-remove", function() { + $container.remove(); + for(var oIdx = 0; oIdx < objects.length; oIdx++) { + if(objects[oIdx].identifier.identifier === object.identifier.identifier) { + objects.splice(oIdx, 1); + } + } + }); + $readed.append($container.append($identifier).append($removeBtn)); } + var barcodeReaderLocalEventListener = barcodeReaderEventListener(gatherReaded); document.addEventListener('keyup', barcodeReaderLocalEventListener); @@ -175,12 +187,12 @@ var BarcodeUtil = new function() { }); var $btnAccept = $('<input>', { 'type': 'submit', 'class' : 'btn btn-primary', 'value' : 'Save Barcode' }); - $btnAccept.prop("disabled",true); - + $btnAccept.prop("disabled",false); var $barcodeReader = $('<input>', { 'type': 'text', 'placeholder': 'barcode', 'style' : 'min-width: 50%;' }); $barcodeReader.keyup(function() { - if($barcodeReader.val().length === 36) { + if($barcodeReader.val().length >= MIN_BARCODE_LENGTH || + $barcodeReader.val().length === 0) { $btnAccept.prop("disabled", false); } else { $btnAccept.prop("disabled", true); @@ -189,20 +201,45 @@ var BarcodeUtil = new function() { $btnAccept.click(function(event) { Util.blockUINoMessage(); - require([ "as/dto/sample/update/SampleUpdate", "as/dto/sample/id/SamplePermId" ], - function(SampleUpdate, SamplePermId) { - var sample = new SampleUpdate(); - sample.setSampleId(new SamplePermId(entity.permId)); - sample.setProperty("$BARCODE", $barcodeReader.val()); - mainController.openbisV3.updateSamples([ sample ]).done(function(result) { - Util.unblockUI(); - Util.showInfo("Barcode Updated", function() { - mainController.changeView('showViewSamplePageFromPermId', entity.permId); - }, true); - }).fail(function(result) { - Util.showFailedServerCallError(result); + + var updateBarcode = function() { + require([ "as/dto/sample/update/SampleUpdate", "as/dto/sample/id/SamplePermId" ], + function(SampleUpdate, SamplePermId) { + var sample = new SampleUpdate(); + sample.setSampleId(new SamplePermId(entity.permId)); + sample.setProperty("$BARCODE", $barcodeReader.val()); + mainController.openbisV3.updateSamples([ sample ]).done(function(result) { + Util.unblockUI(); + Util.showInfo("Barcode Updated", function() { + mainController.changeView('showViewSamplePageFromPermId', entity.permId); + }, true); + }).fail(function(result) { + Util.showFailedServerCallError(result); + }); }); - }); + } + + if($barcodeReader.val().length === 0) { + updateBarcode(); + } else { + var criteria = { + entityKind : "SAMPLE", + logicalOperator : "OR", + rules : { + "UUIDv4-1": { type: "Property/Attribute", name: "PROP.$BARCODE", operator : "thatEqualsString", value: $barcodeReader.val() } + } + }; + mainController.serverFacade.searchForSamplesAdvanced(criteria, { + only : true, + withProperties : true + }, function(results) { + if(results.objects.length === 0) { + updateBarcode(); + } else { + Util.showError("Barcode already in use by " + results.objects[0].identifier.identifier + " : It will not be assigned."); + } + }); + } }); var $btnCancel = $('<input>', { 'type': 'submit', 'class' : 'btn', 'value' : 'Close' }); @@ -212,6 +249,9 @@ var BarcodeUtil = new function() { $window.append($('<legend>').append("Update Barcode")); $window.append($('<br>')); + $window.append(FormUtil.getInfoText("A valid barcode need to have " + MIN_BARCODE_LENGTH + " or more characters.")); + $window.append(FormUtil.getWarningText("An empty barcode will delete the current barcode.")); + $window.append($('<br>')); $window.append($('<center>').append($barcodeReader)); $window.append($('<br>')); $window.append($btnAccept).append(' ').append($btnCancel); @@ -294,10 +334,10 @@ var BarcodeUtil = new function() { value : "qrcode", label : "QR Code" }, -// { -// value : "microqrcode", -// label : "Micro QR Code" -// } + { + value : "microqrcode", + label : "Micro QR Code" + } ]; } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js index d62411f7d2f115461c28686c5dda2d8e1bee915d..8880d8124a5a72fa827f92e9fb9a53f39381d194 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js @@ -1233,6 +1233,13 @@ var FormUtil = new function() { .append($("<span>").text(infoText)); } + this.getWarningText = function(infoText) { + return $("<p>") + .append($("<div>", { class : "glyphicon glyphicon-warning-sign" }) + .css("margin-right", "3px")) + .append($("<span>").text(infoText)); + } +     //     // DSS disk space usage dialog     // diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js index d5ef0f6b7b0ad91f0d61578278077a66e070ac12..4d6e536903007d92ca685bdc9b2dd0b0b0af8382 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormController.js @@ -30,17 +30,20 @@ function DataSetFormController(parentController, mode, entity, dataSet, isMini, if(mode !== FormMode.CREATE) { var datasetPermId = dataSet.code; require([ "as/dto/dataset/id/DataSetPermId", "as/dto/dataset/fetchoptions/DataSetFetchOptions" ], - function(DataSetPermId, DataSetFetchOptions) { - var ids = [new DataSetPermId(datasetPermId)]; - var fetchOptions = new DataSetFetchOptions(); - fetchOptions.withLinkedData().withExternalDms(); - fetchOptions.withExperiment(); - fetchOptions.withSample(); - mainController.openbisV3.getDataSets(ids, fetchOptions).done(function(map) { - _this._dataSetFormModel.v3_dataset = map[datasetPermId]; - _this._dataSetFormModel.linkedData = map[datasetPermId].linkedData; - _this._dataSetFormView.repaint(views); - }); + function(DataSetPermId, DataSetFetchOptions) { + var ids = [new DataSetPermId(datasetPermId)]; + var fetchOptions = new DataSetFetchOptions(); + fetchOptions.withLinkedData().withExternalDms(); + fetchOptions.withExperiment(); + fetchOptions.withSample(); + mainController.openbisV3.getDataSets(ids, fetchOptions).done(function(map) { + _this._dataSetFormModel.v3_dataset = map[datasetPermId]; + _this._dataSetFormModel.linkedData = map[datasetPermId].linkedData; + mainController.openbisV3.getRights(ids, null).done(function(rightsByIds) { + _this._dataSetFormModel.rights = rightsByIds[datasetPermId]; + _this._dataSetFormView.repaint(views); + }); + }); }); } else { _this._dataSetFormView.repaint(views); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js index b7ebff7caf5697c5d113ed1ddb4d04bb301bbdcb..e3065cd23f7a0addcc83ba3ea685b5c4fe702d55 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/DataSetForm/DataSetFormView.js @@ -219,6 +219,10 @@ function DataSetFormView(dataSetFormController, dataSetFormModel) { if(!this._dataSetFormModel.isMini) { var $header = views.header; $header.append($title); + var dataSetTypeDefinitionsExtension = profile.dataSetTypeDefinitionsExtension[_this._dataSetFormModel.dataSet.dataSetTypeCode]; + if(dataSetTypeDefinitionsExtension && dataSetTypeDefinitionsExtension.extraToolbar) { + toolbarModel = toolbarModel.concat(dataSetTypeDefinitionsExtension.extraToolbar(_this._dataSetFormModel.mode, _this._dataSetFormModel.dataSet)); + } $header.append(FormUtil.getToolbar(toolbarModel)); } @@ -803,7 +807,9 @@ function DataSetFormView(dataSetFormController, dataSetFormModel) { this._allowedToEdit = function() { var dataSet = this._dataSetFormModel.v3_dataset; - return dataSet.frozen == false; + var rights = this._dataSetFormModel.rights; + var updateAllowed = rights && rights.rights.indexOf("UPDATE") >= 0; + return updateAllowed && dataSet.frozen == false; } this._allowedToMove = function() { diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js index ad10d568e22e48c33d98bc4bdbd56310f5856f5a..8109e976e41de632e59fe3e2e4c24e0b317a4caa 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormController.js @@ -28,9 +28,12 @@ function ExperimentFormController(mainController, mode, experiment) { var fetchOptions = new ExperimentFetchOptions(); fetchOptions.withProject().withSpace(); mainController.openbisV3.getExperiments([ id ], fetchOptions).done(function(map) { - _this._experimentFormModel.v3_experiment = map[id]; - _this._experimentFormView.repaint(views); - }); + _this._experimentFormModel.v3_experiment = map[id]; + mainController.openbisV3.getRights([ id ], null).done(function(rightsByIds) { + _this._experimentFormModel.rights = rightsByIds[id]; + _this._experimentFormView.repaint(views); + }); + }); }); } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js index c53b3a01e185d1838f9912a43e27e1f8ab8b04d4..99c98af8f23650988552da95068804f915140e36 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ExperimentForm/ExperimentFormView.js @@ -575,7 +575,8 @@ function ExperimentFormView(experimentFormController, experimentFormModel) { this._allowedToEdit = function() { var experiment = this._experimentFormModel.v3_experiment; - return experiment.frozen == false; + var updateAllowed = this._experimentFormModel.rights.rights.indexOf("UPDATE") >= 0; + return updateAllowed && experiment.frozen == false; } this._allowedToMove = function() { diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js index df2a9b1fce2acd81bf794a0f7702ad527bde11a2..9dfddc01d5e6f63341579f63301c54db181a180c 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js @@ -22,7 +22,7 @@ function ResearchCollectionExportController(parentController) { researchCollectionExportView.repaint(views); }; - this.initialiseSubmissionTypesDropdown = function() { + this.initialiseSubmissionTypesDropdown = function(callback) { Util.blockUI(); mainController.serverFacade.listSubmissionTypes(function(error, result) { Util.unblockUI(); @@ -44,9 +44,12 @@ function ResearchCollectionExportController(parentController) { var _this = this; var selectedNodes = $(researchCollectionExportModel.tree).fancytree('getTree').getSelectedNodes(); - var selectedOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected"); - var submissionUrl = selectedOption.val(); - var submissionType = selectedOption.text(); + var selectedStOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected"); + var submissionUrl = selectedStOption.val(); + var submissionType = selectedStOption.text(); + + var selectedRpOption = researchCollectionExportView.$retentionPeriodDropdown.find(":selected"); + var retentionPeriod = selectedRpOption.val(); var toExport = []; for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) { @@ -59,11 +62,13 @@ function ResearchCollectionExportController(parentController) { } 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 if (!submissionUrl) { - Util.showInfo('First select submission type.'); + Util.showInfo('Select submission type.'); + } else if (!retentionPeriod) { + Util.showInfo('Select retention period.'); } else { Util.blockUI(); this.getUserInformation(function(userInformation) { - mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, userInformation, + mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, retentionPeriod, userInformation, function(operationExecutionPermId) { _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) { Util.unblockUI(); @@ -72,11 +77,7 @@ function ResearchCollectionExportController(parentController) { win.focus(); mainController.refreshView(); } else { - if (error) { - Util.showError(error); - } else { - Util.showError('Returned result format is not correct.'); - } + Util.showError(error ? error : 'Returned result format is not correct.'); } }); }); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js index 7099e83d87552bbebcdff0b2788f00a34d18d6e1..b68c871b9c1af0b70fc6ba3d60d8e16c6aba138d 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js @@ -15,9 +15,34 @@ */ function ResearchCollectionExportModel() { - this.submissionTypes = []; - - this.addSubmissionType = function(submissionType) { - submissionTypes.push(submissionType); - }; + this.submissionTypes = [ + { + label: "Data Collection", + value: "/swordv2/collection/20.500.11850/30" + }, + { + label: "Dataset", + value: "/swordv2/collection/20.500.11850/31" + }, + { + label: "Image", + value: "/swordv2/collection/20.500.11850/32" + }, + { + label: "Model", + value: "/swordv2/collection/20.500.11850/33" + }, + { + label: "Sound", + value: "/swordv2/collection/20.500.11850/35" + }, + { + label: "Video", + value: "/swordv2/collection/20.500.11850/36" + }, + { + label: "Other Research Data", + value: "/swordv2/collection/20.500.11850/37" + } + ]; } \ 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/ResearchCollectionExport/ResearchCollectionExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js index fa97054bbcc0ef9b17d3cc9d3cdb4c2e1ff6beae..b3ea2436cc3fb85ec879017cfab2e151d6ba0c2a 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js @@ -16,8 +16,6 @@ function ResearchCollectionExportView(researchCollectionExportController, researchCollectionExportModel) { this.repaint = function(views) { - researchCollectionExportController.initialiseSubmissionTypesDropdown(); - var $header = views.header; var $container = views.content; @@ -44,6 +42,7 @@ function ResearchCollectionExportView(researchCollectionExportController, resear $container.append($form); this.paintSubmissionTypeDropdown($container); + this.paintRetentionPeriodDropdown($container); researchCollectionExportModel.tree = TreeUtil.getCompleteTree($tree); @@ -57,17 +56,38 @@ function ResearchCollectionExportView(researchCollectionExportController, resear this.paintSubmissionTypeDropdown = function($container) { this.$submissionTypeDropdown = this.getSubmissionTypeDropdown(); - var entityTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true); - entityTypeDropdownFormGroup.css('width', '50%'); - $container.append(entityTypeDropdownFormGroup); + var submissionTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true); + submissionTypeDropdownFormGroup.css('width', '50%'); + $container.append(submissionTypeDropdownFormGroup); + }; + + this.paintRetentionPeriodDropdown = function($container) { + this.$retentionPeriodDropdown = this.getRetentionPeriodDropdown(); + var retentionPeriodDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$retentionPeriodDropdown, 'Retention Period', null, true); + retentionPeriodDropdownFormGroup.css('width', '50%'); + $container.append(retentionPeriodDropdownFormGroup); }; this.getSubmissionTypeDropdown = function() { return FormUtil.getDropdown(researchCollectionExportModel.submissionTypes, 'Select a submission type'); }; - this.refreshSubmissionTypeDropdown = function() { - FormUtil.setValuesToComponent(this.$submissionTypeDropdown, researchCollectionExportModel.submissionTypes); - } + this.getRetentionPeriodDropdown = function() { + var values = [ + { + value: '10 years', + label: '10 years' + }, + { + value: '15 years', + label: '15 years' + }, + { + value: 'indefinite', + label: 'indefinite' + } + ]; + return FormUtil.getDropdown(values, 'Select a retention period'); + }; } \ 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/SampleForm/SampleFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js index a328c84c9d9bcc087d77d206998f7cda4f25a8ea..f1b2fb066f66a1e19441ee73c40ed9f1533c396d 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormController.js @@ -37,18 +37,21 @@ function SampleFormController(mainController, mode, sample, paginationInfo) { fetchOptions.withParents(); fetchOptions.withChildren(); mainController.openbisV3.getSamples([ id ], fetchOptions).done(function(map) { - _this._sampleFormModel.v3_sample = map[id]; - // - mainController.serverFacade.listDataSetsForSample(_this._sampleFormModel.sample, true, function(datasets) { - if(!datasets.error) { - _this._sampleFormModel.datasets = datasets.result; - } - - //Load view - _this._sampleFormView.repaint(views); - Util.unblockUI(); - }); - }); + _this._sampleFormModel.v3_sample = map[id]; + // + mainController.openbisV3.getRights([ id ], null).done(function(rightsByIds) { + _this._sampleFormModel.rights = rightsByIds[id]; + mainController.serverFacade.listDataSetsForSample(_this._sampleFormModel.sample, true, function(datasets) { + if(!datasets.error) { + _this._sampleFormModel.datasets = datasets.result; + } + + //Load view + _this._sampleFormView.repaint(views); + Util.unblockUI(); + }); + }); + }); }); } else { // if(sample.sampleTypeCode === "ORDER") { diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js index 101c44681eecd3861d7839a7831254c2ff973844..e688f94ca77796037530d7f1655d9e46cc8dc402 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleForm/SampleFormView.js @@ -442,6 +442,10 @@ function SampleFormView(sampleFormController, sampleFormModel) { var $header = views.header; $header.append($formTitle); + var sampleTypeDefinitionsExtension = profile.sampleTypeDefinitionsExtension[_this._sampleFormModel.sample.sampleTypeCode]; + if(sampleTypeDefinitionsExtension && sampleTypeDefinitionsExtension.extraToolbar) { + toolbarModel = toolbarModel.concat(sampleTypeDefinitionsExtension.extraToolbar(_this._sampleFormModel.mode, _this._sampleFormModel.sample)); + } $header.append(FormUtil.getToolbar(toolbarModel)); // @@ -806,7 +810,7 @@ function SampleFormView(sampleFormController, sampleFormModel) { if(propertyType.dataType === "MULTILINE_VARCHAR") { $component = FormUtil.activateRichTextProperties($component, changeEvent(propertyType), propertyType); } else { - alert("Word Processor only works with MULTILINE_VARCHAR data type."); + alert("Word Processor only works with MULTILINE_VARCHAR data type, " + propertyType.code + " is " + propertyType.dataType + "."); } break; case 'Spreadsheet': @@ -815,7 +819,7 @@ function SampleFormView(sampleFormController, sampleFormModel) { JExcelEditorManager.createField($jexcelContainer, this._sampleFormModel.mode, propertyType.code, this._sampleFormModel.sample); $component = $jexcelContainer; } else { - alert("Spreadsheet only works with XML data type."); + alert("Spreadsheet only works with XML data type, " + propertyType.code + " is " + propertyType.dataType + "."); } break; } @@ -1269,7 +1273,8 @@ function SampleFormView(sampleFormController, sampleFormModel) { this._allowedToEdit = function() { var sample = this._sampleFormModel.v3_sample; - return sample.frozen == false; + var updateAllowed = this._sampleFormModel.rights.rights.indexOf("UPDATE") >= 0; + return updateAllowed && sample.frozen == false; } this._allowedToMove = function() { diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js index 787b3cf4ee32ec7eb542cd5b7f0afa72acde69e0..feb9a5b5f2c5dcf1396e57b1c9b7f90fe9d30353 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SettingsForm/SettingsFormController.js @@ -46,13 +46,13 @@ function SettingsFormController(mainController, settingsSample, mode) { switch(widget.Widget) { case "Word Processor": if(property.dataType !== "MULTILINE_VARCHAR") { - Util.showUserError("Word Processor only works with MULTILINE_VARCHAR data type.", function() {}, true); + Util.showUserError("Word Processor only works with MULTILINE_VARCHAR data type, " + property.code + " is " + property.dataType + ".", function() {}, true); return; } break; case "Spreadsheet": if(property.dataType !== "XML") { - Util.showUserError("Spreadsheet only works with XML data type.", function() {}, true); + Util.showUserError("Spreadsheet only works with XML data type, " + property.code + " is " + property.dataType + ".", function() {}, true); return; } break; 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 7112bc215d9ce606cb256e6f8b837cfeeb8ad394..65e1b63b9c5c8f5cc94274ca2969f7f743f51492 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 @@ -594,7 +594,9 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {                 for(var sIdx = 0; sIdx < samples.length; sIdx++) {                     var sample = samples[sIdx];                     var sampleIsExperiment = sample.type.code.indexOf("EXPERIMENT") > -1; -                     var sampleTypeOnNav = profile.sampleTypeDefinitionsExtension[sample.type.code] && profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"]; +                     var sampleTypeOnNav = profile.sampleTypeDefinitionsExtension[sample.type.code] && + profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"] && + !profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV_FOR_PARENT_TYPES"];                     if(sampleIsExperiment || sampleTypeOnNav) {                         var parentIsExperiment = false;                         if(sample.parents) { @@ -733,24 +735,40 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) { } if(sample.type.code.indexOf("EXPERIMENT") > -1 || (profile.sampleTypeDefinitionsExtension[sample.type.code] && profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV"])) { -                             var sampleDisplayName = sample.code; -     if(sample.properties && sample.properties[profile.propertyReplacingCode]) { -         sampleDisplayName = sample.properties[profile.propertyReplacingCode]; -     } -                         var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId(), null); -     var sampleNode = { -                                         displayName: sampleDisplayName, -                                         title : sampleLink, -                                         entityType: "SAMPLE", -                                         key : sample.getPermId().getPermId(), -                                         folder : true, -                                         lazy : true, -                                         view : "showViewSamplePageFromPermId", -                                         viewData: sample.getPermId().getPermId(), -                                         icon : sampleIcon, -                                         registrationDate: sample.registrationDate -                                    }; -     results.push(sampleNode); + var parentTypeCode = samples[0].type.code; + var showOnNavForParentTypes = profile.sampleTypeDefinitionsExtension[sample.type.code]["SHOW_ON_NAV_FOR_PARENT_TYPES"]; + var showSampleOnNav = false; + if(!showOnNavForParentTypes) { + showSampleOnNav = true; + } else { + for(var ptIdx = 0; ptIdx < showOnNavForParentTypes.length; ptIdx++) { + if(parentTypeCode === showOnNavForParentTypes[ptIdx]) { + showSampleOnNav = true; + break; + } + } + } + + if(showSampleOnNav) { + var sampleDisplayName = sample.code; +     if(sample.properties && sample.properties[profile.propertyReplacingCode]) { +         sampleDisplayName = sample.properties[profile.propertyReplacingCode]; +     } +                         var sampleLink = _this.getLinkForNode(sampleDisplayName, sample.getPermId().getPermId(), "showViewSamplePageFromPermId", sample.getPermId().getPermId(), null); +     var sampleNode = { +                                         displayName: sampleDisplayName, +                                         title : sampleLink, +                                         entityType: "SAMPLE", +                                         key : sample.getPermId().getPermId(), +                                         folder : true, +                                         lazy : true, +                                         view : "showViewSamplePageFromPermId", +                                         viewData: sample.getPermId().getPermId(), +                                         icon : sampleIcon, +                                         registrationDate: sample.registrationDate +                                     }; +     results.push(sampleNode); + }                         }                     }     } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js similarity index 99% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js index 2ddf91069a99b6a87aa4067afd49d79b11e7fd72..bc298d1d51318362cf313cdcf5d3025b67ff2ddd 100755 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/FlowCytometryTechnology.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/flow/plugin.js @@ -751,3 +751,5 @@ $.extend(FlowCytometryTechnology.prototype, ELNLIMSPlugin.prototype, { } }); + +profile.plugins.push( new FlowCytometryTechnology()); \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/generic/plugin.js similarity index 100% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/GenericTechnology.js rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/generic/plugin.js diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js similarity index 98% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js index d064fe15a729ff430e894d1b338292136caaa7e7..845f27618cc9e70760b64ce0a0f61abfd2d0cc28 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/LifeSciencesTechnology.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/life-sciences/plugin.js @@ -16,6 +16,8 @@ $.extend(LifeSciencesTechnology.prototype, ELNLIMSPlugin.prototype, { "CHEMICAL" : { "SHOW" : false, "ENABLE_STORAGE" : true, + "SHOW_ON_NAV": true, + "SHOW_ON_NAV_FOR_PARENT_TYPES": ["EXPERIMENTAL_STEP"] }, "ENZYME" : { "SHOW" : false, @@ -231,4 +233,6 @@ $.extend(LifeSciencesTechnology.prototype, ELNLIMSPlugin.prototype, { dataSetFormBottom : function($container, model) { } -}); \ No newline at end of file +}); + +profile.plugins.push( new LifeSciencesTechnology()); \ No newline at end of file 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/plugins/microscopy/plugin.js similarity index 99% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/plugins/MicroscopyTechnology.js rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/plugins/microscopy/plugin.js index 6c5dfe97af5360ab100cd3f55b4ac2af1f306459..eecf50e95286acba71bbb9fded2c6b4ed3bffac9 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/plugins/microscopy/plugin.js @@ -373,3 +373,5 @@ $.extend(MicroscopyTechnology.prototype, ELNLIMSPlugin.prototype, { }); } }); + +profile.plugins.push( new MicroscopyTechnology()); \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py index 7af095aa70a2bb27e997ae05a1d669464da3fd5c..3dbe4b7b1b820b66dcc97015e4c21ad3aaaa1cd7 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py @@ -13,11 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from collections import deque + import jarray # To obtain the openBIS URL from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer; from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException -from collections import deque # Zip Format from java.io import File; from java.io import FileInputStream; @@ -246,7 +247,7 @@ def findEntitiesToExport(params): operationLog.info("Found: " + str(results.getTotalCount()) + " files"); for file in results.getObjects(): entityFound = {"type": "FILE", "permId": permId, "path": file.getPath(), "isDirectory": file.isDirectory(), - "length": file.getFileLength()}; + "length": file.getFileLength(), "registrationDate": dataset.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); return entitiesToExport 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 f44630466dd4df1741ea821304dfa691d8dc1b08..5c10f7faf58e32208c74ce88c52612f83bd60510 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 @@ -17,6 +17,7 @@ import json import os import traceback import xml.etree.ElementTree as ET +from urlparse import urlsplit import datetime import time @@ -35,8 +36,8 @@ from org.eclipse.jetty.client.util import BasicAuthentication from org.eclipse.jetty.http import HttpMethod from org.eclipse.jetty.util.ssl import SslContextFactory -from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, generateFilesInZip, addToZipFile, cleanUp, \ - generateZipFile, checkResponseStatus +from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, addToZipFile, generateZipFile, \ + checkResponseStatus, cleanUp operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.rcExports.py') @@ -50,25 +51,11 @@ def process(tr, params, tableBuilder): if method == 'exportAll': resultUrl = expandAndExport(tr, params) displayResult(resultUrl is not None, tableBuilder, '{"url": "' + resultUrl + '"}' if resultUrl is not None else None) - elif method == 'getSubmissionTypes': - collectionUrls = getSubmissionTypes(tr) - displayResult(collectionUrls is not None, tableBuilder, collectionUrls) -def getSubmissionTypes(tr): - url = getConfigurationProperty(tr, 'service-document-url') - - httpClient = None - try: - httpClient = authenticateUserJava(url, tr) - httpClient.setFollowRedirects(True) - httpClient.start() - - collections = fetchServiceDocument(url, httpClient) - finally: - if httpClient is not None: - httpClient.stop() - return collections +def getBaseUrl(url): + splitUrl = urlsplit(url) + return splitUrl.scheme + '://' + splitUrl.netloc def expandAndExport(tr, params): @@ -118,7 +105,8 @@ def export(entities, tr, params, userInformation): def sendToDSpace(params, tr, tempZipFileName, tempZipFilePath): - depositUrl = str(params.get('submissionUrl')) + serviceDocumentUrl = getConfigurationProperty(tr, 'service-document-url') + depositUrl = getBaseUrl(serviceDocumentUrl) + str(params.get('submissionUrl')) headers = { 'In-Progress': 'true', @@ -193,9 +181,6 @@ def fetchServiceDocument(url, httpClient): def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZipFileName, exportZipFileName, userInformation, entities): # Generates ZIP file which will go to the research collection server - originUrl=params.get('originUrl') - submissionType = str(params.get('submissionType')) - fileMetadata = [ { 'fileName': contentZipFileName, @@ -211,8 +196,8 @@ def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZi addToZipFile(' ' + contentZipFileName, File(contentZipFilePath), zos) - generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath, submissionType=submissionType, - userInformation=userInformation, entities=entities, originUrl=originUrl) + generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath, + userInformation=userInformation, entities=entities, params=params) except Exception as e: operationLog.error('Exception at: ' + traceback.format_exc()) operationLog.error('Exception: ' + str(e)) @@ -224,7 +209,10 @@ def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZi fos.close() -def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, userInformation, entities, originUrl): +def generateXML(zipOutputStream, fileMetadata, exportDirPath, userInformation, entities, params): + originUrl=params.get('originUrl') + submissionType = str(params.get('submissionType')) + ns = { 'mets': 'http://www.loc.gov/METS/', 'xlink': 'http://www.w3.org/1999/xlink', @@ -296,6 +284,12 @@ def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, us publicationDateField.set('qualifier', 'issued') publicationDateField.text = datetime.date.today().strftime('%Y-%m-%d') + openBisApiUrlField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + openBisApiUrlField.set('mdschema', 'ethz') + openBisApiUrlField.set('element', 'identifier') + openBisApiUrlField.set('qualifier', 'openBisApiUrl') + openBisApiUrlField.text = originUrl + '/openbis-test/' + elnLimsURLPattern = '/openbis-test/webapp/eln-lims/?menuUniqueId=null&viewName=' for entity in entities: @@ -337,7 +331,7 @@ def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, us fLocat = ET.SubElement(file, ET.QName(metsNS, 'FLocat')) fLocat.set('LOCTYPE', 'URL') fLocat.set('MIMETYPE', fileMetadatum.get('mimeType')) - fLocat.set('RETENTIONPERIOD', '10 years') + fLocat.set('RETENTIONPERIOD', params.get('retentionPeriod')) fLocat.set(ET.QName(xlinkNS, 'href'), fileMetadatum.get('fileName')) structMap = ET.SubElement(root, ET.QName(metsNS, 'structMap')) 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 ce30b77747f2bcb546070e67ff0759db68dcc6eb..5c17cd7132d3bc10ed4fef8f644d542ba0bad896 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 @@ -9,7 +9,8 @@ from processors import OpenbisDuplicatesHandler, PropertiesLabelHandler, Duplica 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 +from utils.openbis_utils import get_version_name_for, get_metadata_name_for, get_metadata_name_for_existing_element +from parsers.definition_to_creation.creation_types import CreationTypes, VocabularyTermDefinitionToCreationType def validate_data(xls_byte_arrays, csv_strings, update_mode, xls_name): @@ -47,7 +48,7 @@ def save_versioning_information(versioning_information, xls_version_filepath): def create_versioning_information(all_versioning_information, creations, creations_metadata, update_mode, xls_version_name): if xls_version_name in all_versioning_information: - versioning_information = all_versioning_information[xls_version_name] + versioning_information = all_versioning_information[xls_version_name].copy() for creation_type, creation_collection in creations.items(): if creation_type in versionable_types: for creation in creation_collection: @@ -69,6 +70,30 @@ def create_versioning_information(all_versioning_information, creations, creatio return versioning_information +def checkDataConsistency(existing_elements, all_versioning_information, xls_version_name, creations): + #check that data from json exist in DB + + if xls_version_name not in all_versioning_information: + return + + versioning_information = all_versioning_information[xls_version_name] + + existing_elements_dict = set() + + for existing_type, elements in existing_elements.items(): + for element in elements: + existing_elements_dict.add(get_metadata_name_for_existing_element(existing_type, element)) + + for creation_type, creation_collection in creations.items(): + if creation_type in versionable_types: + for creation in creation_collection: + code = get_metadata_name_for(creation_type, creation) + + if code in versioning_information and code not in existing_elements_dict: + raise Exception("xls-import-version-info.json contains creation = '" + code + "' with creation_type = '" + \ + creation_type + "' that is not in the database. Please edit the file or delete it.") + + def process(context, parameters): """ Excel import AS service. @@ -109,6 +134,7 @@ def process(context, parameters): versioning_information = create_versioning_information(all_versioning_information, creations, creations_metadata, update_mode, xls_version_name) existing_elements = search_engine.find_all_existing_elements(creations) + checkDataConsistency(existing_elements, all_versioning_information, xls_version_name, creations) entity_kinds = search_engine.find_existing_entity_kind_definitions_for(creations) existing_vocabularies = search_engine.find_all_existing_vocabularies() existing_unified_kinds = unify_properties_representation_of(creations, entity_kinds, existing_vocabularies, diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py index 49299827ae757d863dfc48a7c3c7dc6ff4c70f2a..35d2998b3614f265de85917a3e66b9f2a82eaad9 100644 --- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py +++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation/creation_parsers.py @@ -18,7 +18,7 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id import ProjectIdentifie from ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id import ExperimentIdentifier from java.lang import UnsupportedOperationException from ch.systemsx.cisd.common.exceptions import UserFailureException -from utils.openbis_utils import is_internal_namespace, get_script_name_for +from utils.openbis_utils import is_internal_namespace, get_script_name_for, upper_case_code from .creation_types import PropertyTypeDefinitionToCreationType, VocabularyDefinitionToCreationType, \ VocabularyTermDefinitionToCreationType, \ PropertyAssignmentDefinitionToCreationType, SampleTypeDefinitionToCreationType, \ @@ -73,13 +73,12 @@ class PropertyTypeDefinitionToCreationParser(object): for prop in definition.properties: property_type_creation = PropertyTypeCreation() - code = prop.get(u'code') - code = code.upper() if code is not None else None + code = upper_case_code(prop.get(u'code')) property_type_creation.code = code property_type_creation.label = prop.get(u'property label') property_type_creation.description = prop.get(u'description') property_type_creation.dataType = DataType.valueOf(prop.get(u'data type')) - property_type_creation.internalNameSpace = is_internal_namespace(prop.get(u'code')) + property_type_creation.internalNameSpace = is_internal_namespace(upper_case_code(prop.get(u'code'))) property_type_creation.vocabularyId = VocabularyPermId(prop.get(u'vocabulary code')) if prop.get( u'vocabulary code') is not None else None metadata = json.loads(prop.get(u'metadata')) if prop.get(u'metadata') is not None else None @@ -95,8 +94,7 @@ class PropertyTypeDefinitionToCreationParser(object): class VocabularyDefinitionToCreationParser(object): def parse(self, definition): - code = definition.attributes.get(u'code') - code = code.upper() if code is not None else None + code = upper_case_code(definition.attributes.get(u'code')) vocabulary_creation = VocabularyCreation() vocabulary_creation.code = code vocabulary_creation.internalNameSpace = is_internal_namespace(code) @@ -111,11 +109,11 @@ class VocabularyDefinitionToCreationParser(object): class VocabularyTermDefinitionToCreationParser(object): def parse(self, definition): - vocabulary_code = VocabularyPermId(definition.attributes.get(u'code')) + vocabulary_code = VocabularyPermId(upper_case_code(definition.attributes.get(u'code'))) vocabulary_creations_terms = [] for prop in definition.properties: vocabulary_creation_term = VocabularyTermCreation() - vocabulary_creation_term.code = prop.get(u'code') + vocabulary_creation_term.code = upper_case_code(prop.get(u'code')) vocabulary_creation_term.label = prop.get(u'label') vocabulary_creation_term.description = prop.get(u'description') vocabulary_creation_term.vocabularyId = vocabulary_code @@ -130,7 +128,7 @@ class VocabularyTermDefinitionToCreationParser(object): class PropertyAssignmentDefinitionToCreationParser(object): def parse(self, prop): - code = prop.get(u'code') + code = upper_case_code(prop.get(u'code')) property_assignment_creation = PropertyAssignmentCreation() is_mandatory = get_boolean_from_string(prop.get(u'mandatory')) property_assignment_creation.mandatory = is_mandatory @@ -151,7 +149,7 @@ class PropertyAssignmentDefinitionToCreationParser(object): class SampleTypeDefinitionToCreationParser(object): def parse(self, definition): - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) sample_creation = SampleTypeCreation() sample_creation.code = code should_auto_generate_codes = get_boolean_from_string(definition.attributes.get(u'auto generate codes')) @@ -181,7 +179,7 @@ class SampleTypeDefinitionToCreationParser(object): class ExperimentTypeDefinitionToCreationParser(object): def parse(self, definition): - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) experiment_type_creation = ExperimentTypeCreation() experiment_type_creation.code = code experiment_type_creation.description = definition.attributes.get(u'description') @@ -208,7 +206,7 @@ class DatasetTypeDefinitionToCreationParser(object): def parse(self, definition): dataset_type_creation = DataSetTypeCreation() - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) dataset_type_creation.code = code dataset_type_creation.description = definition.attributes.get(u'description') if u'validation script' in definition.attributes and definition.attributes.get( @@ -235,9 +233,9 @@ class SpaceDefinitionToCreationParser(object): space_creations = [] for prop in definition.properties: space_creation = SpaceCreation() - space_creation.code = prop.get(u'code') + space_creation.code = upper_case_code(prop.get(u'code')) space_creation.description = prop.get(u'description') - creation_id = prop.get(u'code') + creation_id = upper_case_code(prop.get(u'code')) space_creation.creationId = CreationId(creation_id) space_creations.append(space_creation) return space_creations @@ -252,10 +250,10 @@ class ProjectDefinitionToCreationParser(object): project_creations = [] for prop in definition.properties: project_creation = ProjectCreation() - project_creation.code = prop.get(u'code') + project_creation.code = upper_case_code(prop.get(u'code')) project_creation.description = prop.get(u'description') project_creation.spaceId = CreationId(prop.get(u'space')) - creation_id = prop.get(u'code') + creation_id = upper_case_code(prop.get(u'code')) project_creation.creationId = CreationId(creation_id) project_creations.append(project_creation) return project_creations @@ -272,9 +270,9 @@ class ExperimentDefinitionToCreationParser(object): for experiment_properties in definition.properties: experiment_creation = ExperimentCreation() experiment_creation.typeId = EntityTypePermId(definition.attributes.get(u'experiment type')) - experiment_creation.code = experiment_properties.get(u'code') + experiment_creation.code = upper_case_code(experiment_properties.get(u'code')) experiment_creation.projectId = ProjectIdentifier(experiment_properties.get(u'project')) - creation_id = experiment_properties.get(u'code') + creation_id = upper_case_code(experiment_properties.get(u'code')) experiment_creation.creationId = CreationId(creation_id) for prop, value in experiment_properties.items(): if prop not in project_attributes: @@ -296,8 +294,8 @@ class SampleDefinitionToCreationParser(object): sample_creation = SampleCreation() sample_creation.typeId = EntityTypePermId(definition.attributes.get(u'sample type')) if u'code' in sample_properties and sample_properties.get(u'code') is not None: - sample_creation.code = sample_properties.get(u'code') - creation_id = sample_properties.get(u'code') + sample_creation.code = upper_case_code(sample_properties.get(u'code')) + creation_id = upper_case_code(sample_properties.get(u'code')) sample_creation.creationId = CreationId(creation_id) if u'$' in sample_properties and sample_properties.get(u'$') is not None: # may overwrite creationId from code, which is intended @@ -347,7 +345,7 @@ class ScriptDefinitionToCreationParser(object): if u'validation script' in definition.attributes: validation_script_path = definition.attributes.get(u'validation script') if validation_script_path is not None: - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) validation_script_creation = PluginCreation() script = self.context.get_script(validation_script_path) validation_script_creation.name = get_script_name_for(code, validation_script_path) @@ -358,7 +356,7 @@ class ScriptDefinitionToCreationParser(object): for prop in definition.properties: if u'dynamic script' in prop: dynamic_prop_script_path = prop.get(u'dynamic script') - code = prop.get(u'code') + code = upper_case_code(prop.get(u'code')) if dynamic_prop_script_path is not None: validation_script_creation = PluginCreation() script = self.context.get_script(dynamic_prop_script_path) diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py index aed19d6a9ad271b8da129da544d1aa9d631ff183..cbb2d3b2e7f7094fb6a1ccfd9878899fedd14877 100644 --- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py +++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/parsers/definition_to_creation_metadata/creation_metadata_parsers.py @@ -3,7 +3,7 @@ from ..definition_to_creation import PropertyTypeDefinitionToCreationType, Vocab SampleTypeDefinitionToCreationType, ExperimentTypeDefinitionToCreationType, DatasetTypeDefinitionToCreationType, \ SpaceDefinitionToCreationType, ProjectDefinitionToCreationType, ExperimentDefinitionToCreationType, \ SampleDefinitionToCreationType, ScriptDefinitionToCreationType -from utils.openbis_utils import get_metadata_name_for +from utils.openbis_utils import get_metadata_name_for, upper_case_code from ch.systemsx.cisd.common.exceptions import UserFailureException from utils.dotdict import dotdict @@ -56,8 +56,7 @@ class PropertyTypeDefinitionToCreationMetadataParser(object): for prop in definition.properties: property_creation_metadata = dotdict() - code = prop.get(u'code') - code = code.upper() if code is not None else None + code = upper_case_code(prop.get(u'code')) property_creation_metadata.code = code property_creation_metadata.version = get_version(prop.get(u'version', 1)) creation_metadata[code] = property_creation_metadata @@ -72,12 +71,12 @@ class VocabularyDefinitionToCreationMetadataParser(object): def parse(self, definition): creation_metadata = dotdict() vocabulary_creation_metadata = dotdict() - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) vocabulary_creation_metadata.code = code vocabulary_creation_metadata.version = get_version(definition.attributes.get(u'version', 1)) vocabulary_creation_metadata.terms = dotdict() for prop in definition.properties: - term_code = prop.get(u'code') + term_code = upper_case_code(prop.get(u'code')) creation_term_metadata = dotdict() creation_term_metadata.code = term_code creation_term_metadata.version = get_version(prop.get(u'version', 1)) @@ -95,7 +94,7 @@ class SampleTypeDefinitionToCreationMetadataParser(object): def parse(self, definition): creation_metadata = dotdict() sample_type_creation_metadata = dotdict() - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) sample_type_creation_metadata.code = code sample_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1)) creation_metadata[code] = sample_type_creation_metadata @@ -111,7 +110,7 @@ class ExperimentTypeDefinitionToCreationMetadataParser(object): def parse(self, definition): creation_metadata = dotdict() experiment_type_creation_metadata = dotdict() - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) experiment_type_creation_metadata.code = code experiment_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1)) creation_metadata[code] = experiment_type_creation_metadata @@ -127,7 +126,7 @@ class DatasetTypeDefinitionToCreationMetadataParser(object): def parse(self, definition): creation_metadata = dotdict() dataset_type_creation_metadata = dotdict() - code = definition.attributes.get(u'code') + code = upper_case_code(definition.attributes.get(u'code')) dataset_type_creation_metadata.code = code dataset_type_creation_metadata.version = get_version(definition.attributes.get(u'version', 1)) creation_metadata[code] = dataset_type_creation_metadata diff --git a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py index e347dc16b43b4241ec13ab3c87fbfc52f0b2965a..bc632d631da35d371f16f0de0cd4bff4ba32c31d 100644 --- a/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py +++ b/openbis_standard_technologies/dist/core-plugins/xls-import/1/as/services/xls-import-api/utils/openbis_utils.py @@ -48,3 +48,20 @@ def get_metadata_name_for(creation_type, creation): code = "{}-{}".format(creation_type, creation.code) code = code.upper() return code + + +def get_metadata_name_for_existing_element(existing_type, existing_element): + if existing_type == VocabularyTermDefinitionToCreationType: + code = str(existing_element.permId).split("(") + code = code[1].split(")") + code = "{}-{}".format(code[0], existing_element.code) + else: + code = existing_element.code + + code = "{}-{}".format(existing_type, code) + code = code.upper() + return code + + +def upper_case_code(code): + return code.upper() if code is not None else None \ No newline at end of file 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 193174e4d5c77a34c4cc2f2c2f21abe1f62f9ecd..82698022b4f2736bbc75317c657ee309f520291d 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 @@ -15,7 +15,9 @@ import ch.systemsx.cisd.openbis.generic.shared.coreplugin.CorePluginsUtils; public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests { - private String XLS_VERSIONING_DIR = "xls-import.version-data-file"; + private static final String VERSIONING_JSON = "./versioning.json"; + + private static final String XLS_VERSIONING_DIR = "xls-import.version-data-file"; private static final String TEST_USER = "test"; @@ -30,7 +32,7 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext @BeforeSuite public void setupSuite() { - System.setProperty(XLS_VERSIONING_DIR, "./versioning.bin"); + System.setProperty(XLS_VERSIONING_DIR, VERSIONING_JSON); System.setProperty(CorePluginsUtils.CORE_PLUGINS_FOLDER_KEY, "dist/core-plugins"); System.setProperty(Constants.ENABLED_MODULES_KEY, "xls-import"); TestInitializer.initEmptyDbNoIndex(); @@ -39,13 +41,11 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext @BeforeMethod public void beforeTest() { sessionToken = v3api.login(TEST_USER, PASSWORD); - System.out.println("AHAHHAHAHAHHA"); - System.out.println(sessionToken); } @AfterMethod public void afterTest() { - File f = new File("./versioning.bin"); + File f = new File(VERSIONING_JSON); f.delete(); v3api.logout(sessionToken); } 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 7db73a88f35d6717cc265e4b5ac14f73c75a1f3f..b2bb2ec0ee40133a2fa1adda67ddd4726f14c587 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 @@ -7,16 +7,16 @@ import static org.testng.Assert.assertTrue; import java.io.IOException; import java.nio.file.Paths; +import java.util.Arrays; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.delete.PropertyTypeDeletionOptions; 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 ch.ethz.sis.openbis.generic.asapi.v3.dto.property.DataType; @@ -154,4 +154,18 @@ public class ImportPropertyTypesTest extends AbstractImportTest { TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE))); } + @Test(expectedExceptions = Exception.class) + @DirtiesContext + public void deleteProjectFromDBButNotFromJSON() throws IOException { + // GIVEN + TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + // WHEN + PropertyType type = TestUtils.getPropertyType(v3api, sessionToken, "$INTERNAL_PROP"); + PropertyTypeDeletionOptions deletionOptions = new PropertyTypeDeletionOptions(); + deletionOptions.setReason("test"); + v3api.deletePropertyTypes(sessionToken, Arrays.asList(type.getPermId()), deletionOptions); + // THEN + TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + } + } diff --git a/pybis/src/python/CHANGELOG.md b/pybis/src/python/CHANGELOG.md index be9a687de2bbb1af16ee2086db232bcb1f38c897..5ed3891a4524ff8a4327926456d5bb6ca2bcd2f4 100644 --- a/pybis/src/python/CHANGELOG.md +++ b/pybis/src/python/CHANGELOG.md @@ -1,3 +1,8 @@ +## Changes with pybis-1.10.1 + +* fixed a nasty threading bug: open threads are now closed when downloading or uploading datasets +* this bugfix avoids this RuntimeError: can't start new thread + ## Changes with pybis-1.10.0 * dataSet upload now supports zipfiles diff --git a/pybis/src/python/pybis/__init__.py b/pybis/src/python/pybis/__init__.py index 054013efef119a95e9c774b1a7e04f59df677c73..df207bd89b7c849af3505368c59aa3749fe7c077 100644 --- a/pybis/src/python/pybis/__init__.py +++ b/pybis/src/python/pybis/__init__.py @@ -1,7 +1,7 @@ name = 'pybis' __author__ = 'Swen Vermeul' __email__ = 'swen@ethz.ch' -__version__ = '1.10.0' +__version__ = '1.10.1' from . import pybis from .pybis import Openbis diff --git a/pybis/src/python/pybis/dataset.py b/pybis/src/python/pybis/dataset.py index 982977b0e67590603e3c0f327d072bd3b5feecda..6e893eb7d11100fe840e16e97cc7ea4b6a51f0b7 100644 --- a/pybis/src/python/pybis/dataset.py +++ b/pybis/src/python/pybis/dataset.py @@ -1,7 +1,7 @@ import os from threading import Thread -from tabulate import tabulate from queue import Queue +from tabulate import tabulate from .openbis_object import OpenBisObject from .definitions import openbis_definitions from .utils import VERBOSE @@ -241,23 +241,21 @@ class DataSet( """ base_url = self.data['dataStore']['downloadUrl'] + '/datastore_server/' + self.permId + '/' + with DataSetDownloadQueue(workers=workers) as queue: + # get file list and start download + for filename in files: + file_info = self.get_file_list(start_folder=filename) + file_size = file_info[0]['fileSize'] + download_url = base_url + filename + '?sessionID=' + self.openbis.token + filename_dest = os.path.join(destination, self.permId, filename) + queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, 'wb']) - queue = DataSetDownloadQueue(workers=workers) - - # get file list and start download - for filename in files: - file_info = self.get_file_list(start_folder=filename) - file_size = file_info[0]['fileSize'] - download_url = base_url + filename + '?sessionID=' + self.openbis.token - filename_dest = os.path.join(destination, self.permId, filename) - queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, 'wb']) + # wait until all files have downloaded + if wait_until_finished: + queue.join() - # wait until all files have downloaded - if wait_until_finished: - queue.join() - - if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId)) - return destination + if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId)) + return destination def _download_link(self, files, destination, wait_until_finished, workers, linked_dataset_fileservice_url, content_copy_index): @@ -265,42 +263,42 @@ class DataSet( Requires the microservice server to be running at the given linked_dataset_fileservice_url. """ - queue = DataSetDownloadQueue(workers=workers, collect_files_with_wrong_length=True) + with DataSetDownloadQueue(workers=workers, collect_files_with_wrong_length=True) as queue: - if content_copy_index >= len(self.data["linkedData"]["contentCopies"]): - raise ValueError("Content Copy index out of range.") - content_copy = self.data["linkedData"]["contentCopies"][content_copy_index] + if content_copy_index >= len(self.data["linkedData"]["contentCopies"]): + raise ValueError("Content Copy index out of range.") + content_copy = self.data["linkedData"]["contentCopies"][content_copy_index] - for filename in files: - file_info = self.get_file_list(start_folder=filename) - file_size = file_info[0]['fileSize'] + for filename in files: + file_info = self.get_file_list(start_folder=filename) + file_size = file_info[0]['fileSize'] - download_url = linked_dataset_fileservice_url - download_url += "?sessionToken=" + self.openbis.token - download_url += "&datasetPermId=" + self.data["permId"]["permId"] - download_url += "&externalDMSCode=" + content_copy["externalDms"]["code"] - download_url += "&contentCopyPath=" + content_copy["path"].replace("/", "%2F") - download_url += "&datasetPathToFile=" + urllib.parse.quote(filename) + download_url = linked_dataset_fileservice_url + download_url += "?sessionToken=" + self.openbis.token + download_url += "&datasetPermId=" + self.data["permId"]["permId"] + download_url += "&externalDMSCode=" + content_copy["externalDms"]["code"] + download_url += "&contentCopyPath=" + content_copy["path"].replace("/", "%2F") + download_url += "&datasetPathToFile=" + urllib.parse.quote(filename) - filename_dest = os.path.join(destination, self.permId, filename) + filename_dest = os.path.join(destination, self.permId, filename) - # continue download if file is not complete - do nothing if it is - write_mode = 'wb' - if os.path.exists(filename_dest): - actual_size = os.path.getsize(filename_dest) - if actual_size == int(file_size): - continue - elif actual_size < int(file_size): - write_mode = 'ab' - download_url += "&offset=" + str(actual_size) + # continue download if file is not complete - do nothing if it is + write_mode = 'wb' + if os.path.exists(filename_dest): + actual_size = os.path.getsize(filename_dest) + if actual_size == int(file_size): + continue + elif actual_size < int(file_size): + write_mode = 'ab' + download_url += "&offset=" + str(actual_size) - queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, write_mode]) + queue.put([download_url, filename, filename_dest, file_size, self.openbis.verify_certificates, write_mode]) - if wait_until_finished: - queue.join() + if wait_until_finished: + queue.join() - if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId)) - return destination, queue.files_with_wrong_length + if VERBOSE: print("Files downloaded to: %s" % os.path.join(destination, self.permId)) + return destination, queue.files_with_wrong_length @property @@ -584,50 +582,60 @@ class DataSet( # define a queue to handle the upload threads - queue = DataSetUploadQueue() + with DataSetUploadQueue() as queue: - real_files = [] - for filename in files: - if os.path.isdir(filename): - real_files.extend( - [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(filename)) for f in fn]) - else: - real_files.append(os.path.join(filename)) - - # compose the upload-URL and put URL and filename in the upload queue - for filename in real_files: - file_in_wsp = os.path.join(folder, os.path.basename(filename)) - url_filename = os.path.join(folder, urllib.parse.quote(os.path.basename(filename))) - self.files_in_wsp.append(file_in_wsp) - - upload_url = ( - datastore_url + '/datastore_server/session_workspace_file_upload' - + '?filename=' + url_filename - + '&id=1' - + '&startByte=0&endByte=0' - + '&sessionID=' + self.openbis.token - ) - queue.put([upload_url, filename, self.openbis.verify_certificates]) + real_files = [] + for filename in files: + if os.path.isdir(filename): + real_files.extend( + [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(filename)) for f in fn]) + else: + real_files.append(os.path.join(filename)) + + # compose the upload-URL and put URL and filename in the upload queue + for filename in real_files: + file_in_wsp = os.path.join(folder, os.path.basename(filename)) + url_filename = os.path.join(folder, urllib.parse.quote(os.path.basename(filename))) + self.files_in_wsp.append(file_in_wsp) + + upload_url = ( + datastore_url + '/datastore_server/session_workspace_file_upload' + + '?filename=' + url_filename + + '&id=1' + + '&startByte=0&endByte=0' + + '&sessionID=' + self.openbis.token + ) + queue.put([upload_url, filename, self.openbis.verify_certificates]) - # wait until all files have uploaded - if wait_until_finished: - queue.join() + # wait until all files have uploaded + if wait_until_finished: + queue.join() - # return files with full path in session workspace - return self.files_in_wsp + # return files with full path in session workspace + return self.files_in_wsp class DataSetUploadQueue(): def __init__(self, workers=20): # maximum files to be uploaded at once self.upload_queue = Queue() + self.workers = workers # define number of threads and start them for t in range(workers): t = Thread(target=self.upload_file) - t.daemon = True t.start() + def __enter__(self, *args, **kwargs): + return self + + def __exit__(self, *args, **kwargs): + """This method is called at the end of a with statement. + """ + # stop the workers + for i in self.workers: + self.upload_queue.put(None) + def put(self, things): """ expects a list [url, filename] which is put into the upload queue """ @@ -636,12 +644,17 @@ class DataSetUploadQueue(): def join(self): """ needs to be called if you want to wait for all uploads to be finished """ + # block until all tasks are done self.upload_queue.join() def upload_file(self): while True: # get the next item in the queue - upload_url, filename, verify_certificates = self.upload_queue.get() + queue_item = self.upload_queue.get() + if queue_item is None: + # when we call the .join() method of the DataSetUploadQueue and empty the queue + break + upload_url, filename, verify_certificates = queue_item filesize = os.path.getsize(filename) @@ -713,14 +726,24 @@ class DataSetDownloadQueue(): def __init__(self, workers=20, collect_files_with_wrong_length=False): self.collect_files_with_wrong_length = collect_files_with_wrong_length # maximum files to be downloaded at once + self.workers = workers self.download_queue = Queue() self.files_with_wrong_length = [] # define number of threads - for t in range(workers): - t = Thread(target=self.download_file) - t.daemon = True - t.start() + for i in range(workers): + thread = Thread(target=self.download_file) + thread.start() + + def __enter__(self, *args, **kwargs): + return self + + def __exit__(self, *args, **kwargs): + """This method is called at the end of a with statement. + """ + # stop all workers + for i in range(self.workers): + self.download_queue.put(None) def put(self, things): """ expects a list [url, filename] which is put into the download queue @@ -735,7 +758,11 @@ class DataSetDownloadQueue(): def download_file(self): while True: try: - url, filename, filename_dest, file_size, verify_certificates, write_mode = self.download_queue.get() + queue_item = self.download_queue.get() + if queue_item is None: + # when we call the .join() method of the DataSetDownloadQueue and empty the queue + break + url, filename, filename_dest, file_size, verify_certificates, write_mode = queue_item # create the necessary directory structure if they don't exist yet os.makedirs(os.path.dirname(filename_dest), exist_ok=True) diff --git a/pybis/src/python/setup.py b/pybis/src/python/setup.py index 85bef8b0fb2bf46a2adbfd898e98cbbbfa6764e9..9f870a55dee228d5fab7046c55a908f94880b4e9 100644 --- a/pybis/src/python/setup.py +++ b/pybis/src/python/setup.py @@ -11,7 +11,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setup( name='PyBIS', - version= '1.10.0', + version= '1.10.1', author='Swen Vermeul • ID SIS • ETH Zürich', author_email='swen@ethz.ch', description='openBIS connection and interaction, optimized for using with Jupyter',