diff --git a/conf/config.properties.example b/conf/config.properties.example index 16531b176..878d3fcf0 100644 --- a/conf/config.properties.example +++ b/conf/config.properties.example @@ -637,6 +637,9 @@ server.systemTest.runDestructive=false # Behavior without this config set to true is to give all users access to all projects server.project.roles.enable=true +# Require project type to be returned by project CustomerSpecificFunctions +server.project.type.required=false + #Controls whether to try to sync projects list from ACAS to CmpdReg server.project.sync.cmpdReg=true diff --git a/modules/GenericDataParser/src/server/generic_data_parser.R b/modules/GenericDataParser/src/server/generic_data_parser.R index e1699ea7c..73a99a2c9 100644 --- a/modules/GenericDataParser/src/server/generic_data_parser.R +++ b/modules/GenericDataParser/src/server/generic_data_parser.R @@ -706,27 +706,58 @@ validateCalculatedResults <- function(calculatedResults, dryRun, curveNames, tes # A project can only be used in the experiment if it's an unrestricted project or belongs to the project which this experiment belongs to projectsDT <- data.table(projectDF) - # Loop through by project code and create a projectAllowedForExperiment column + # Add project type if its missing + if(!"type" %in% colnames(projectsDT)) { + projectsDT[ , type := NA] + } + + # Get the project type of the experiment project if required by configurations + projectType <- NA + if(configList$server.project.type.required) { + projectType <- projectsDT[code == projectCode]$type + if(is.na(projectType)) { + stopUser(paste0("Project '",projectCode,"' does not have a type. Please contact your system administrator.")) + } + } + + # Here we are annotating each of the projects with a boolean as to whether it can be used in the experiment if its used by a lot projectsDT[ , projectAllowedForExperiment := { - # Check if the project is unrestricted or if it equals the projectCode of the experiment - !isRestricted | code == projectCode + # projectType and projectCode are referring to the experiment project + # type, code, and isRestricted are referring to the batch project + if(is.na(projectType)) { + # If the project type is na then this is not a system with project type + # so we just check if the project is restricted and if the lot project is the same as the experiment project + !isRestricted | code == projectCode + } else { + # If project type is not na then this is a system with project type + switch(projectType, + "RESTRICTED" = !isRestricted | code == projectCode, + "HYPER_RESTRICTED" = code == projectCode, + "UNRESTRICTED" = ifelse(identical(type, "HYPER_RESTRICTED"), FALSE, TRUE), + "GLOBAL" = ifelse(identical(type, "HYPER_RESTRICTED"), FALSE, TRUE), + stopUser(paste0("Project '",projectCode,"' has an invalid type. Please contact your system administrator.")) + ) + } + }, by = code] # Merge batch projects to project restrition information batchProjectWithRestrictionInfo <- as.data.table(merge(batchProjects, projectsDT, by.x="Project.Code", by.y="code")) - + # Create a data table with one row per batch with a boolean column as to whether it can be used in the experiment # if any of the projects the batch belongs to is allowed to be loaded to this experiment, then the batch can be loaded to the experiment - canUseBatchDT <- batchProjectWithRestrictionInfo[ , any(projectAllowedForExperiment), by = Requested.Name] + canUseBatchDT <- batchProjectWithRestrictionInfo[ , any(projectAllowedForExperiment), by = c("Requested.Name", "type")] setnames(canUseBatchDT, "V1", "batchCanBeLoadedToExperimentProject") # Get a list of batches which are restricted and cannot be used in this experiment's project - rCompounds <- canUseBatchDT[batchCanBeLoadedToExperimentProject == FALSE]$Requested.Name + rCompounds <- canUseBatchDT[batchCanBeLoadedToExperimentProject == FALSE] - if (length(rCompounds) > 0) { + if (nrow(rCompounds) > 0) { addProjectError <- TRUE - shouldCheckRole <- configList$server.project.roles.enable & !is.null(configList$client.roles.crossProjectLoaderRole) - if(shouldCheckRole) { + crossProjectLoaderRolesEnabled <- configList$server.project.roles.enable & !is.null(configList$client.roles.crossProjectLoaderRole) + # HYPER_RESTRICTED projects are not allowed to be loaded to other projects by anyone + # So only check cross project loader roles if the project is not HYPER_RESTRICTED + if(crossProjectLoaderRolesEnabled && !identical(projectType, "HYPER_RESTRICTED")) { response <- getURL(URLencode(paste0(racas::applicationSettings$server.nodeapi.path, racas::applicationSettings$client.service.users.path, "/", user))) if(response=="") { addError(paste0("Username '",user,"' could not be found in the system")) @@ -740,8 +771,17 @@ validateCalculatedResults <- function(calculatedResults, dryRun, curveNames, tes } } if(addProjectError) { - addError(paste0("Compounds '", paste(rCompounds, collapse = "', '"), + # Return error message for each type of project + # if projectType (experiment project) is restricted then return error message for specific for hyper restrictions + rCompounds[ , { + if(identical(projectType, "HYPER_RESTRICTED")) { + addError(paste0("Compounds '", paste(.SD$Requested.Name, collapse = "', '"), + "' are in a project that does not match the hyper restricted project entered for this ",racas::applicationSettings$client.experiment.label,".")) + } else { + addError(paste0("Compounds '", paste(.SD$Requested.Name, collapse = "', '"), "' are in a restricted project that does not match the one entered for this ",racas::applicationSettings$client.experiment.label,".")) + } + }, by = type] } } } diff --git a/modules/ServerAPI/src/server/CustomerSpecificServerFunctions.coffee b/modules/ServerAPI/src/server/CustomerSpecificServerFunctions.coffee index c8194a43f..acd79dfd1 100644 --- a/modules/ServerAPI/src/server/CustomerSpecificServerFunctions.coffee +++ b/modules/ServerAPI/src/server/CustomerSpecificServerFunctions.coffee @@ -368,6 +368,14 @@ exports.getProjectStubsInternal = (callback) -> #remove groups attribute _.each acasGroupsAndProjects.projects, (project) -> delete project.groups + # ACAS-754: Adding project type to project object + # This is for testing experiment loader with different project types explained in ACAS-754 + # if project.name == "Global" + # project.type = "GLOBAL" + # else if project.isRestricted + # project.type = "HYPER_RESTRICTED" + # else + # project.type = "UNRESTRICTED" callback response.statusCode, acasGroupsAndProjects.projects exports.makeServiceRequestHeaders = (user) -> diff --git a/modules/ServerAPI/src/server/python/acas_ldclient/acasldclient.py b/modules/ServerAPI/src/server/python/acas_ldclient/acasldclient.py index 1223fbc3f..fee27bf7e 100644 --- a/modules/ServerAPI/src/server/python/acas_ldclient/acasldclient.py +++ b/modules/ServerAPI/src/server/python/acas_ldclient/acasldclient.py @@ -6,6 +6,13 @@ from ldclient.api.requester import SUPPORTED_SERVER_VERSION from ldclient.base import version_str_as_tuple +# Prior to Live Design 2024-1, ProjectType was not available in the API +# so we only import it and use it if it is available in order to maintain backwards compatibility +try: + from ldclient.enums import ProjectType +except ImportError: + ProjectType = None + import argparse import json import os, sys @@ -268,15 +275,28 @@ def get_projects(client): return projects def ld_project_to_acas(ld_project): + + # Prior to Live Design 2024-1, the project type was not available in the API + # so we only set it if it is available in order to maintain backwards compatibility + if ProjectType is not None: + project_acls = { + 'isRestricted': ld_project.project_type not in (ProjectType.GLOBAL, ProjectType.UNRESTRICTED), + 'type': ld_project.project_type + } + else: + project_acls = { + 'isRestricted': ld_project.restricted + } + acas_project = { 'id': ld_project.id, 'code': ld_project.name, 'alias': ld_project.name, 'active': True if ld_project.active == "Y" else False, 'ignored': False if ld_project.active == "Y" else True, - 'isRestricted': ld_project.restricted, 'name': ld_project.name } + acas_project.update(project_acls) return acas_project def main():