diff --git a/CHANGELOG.md b/CHANGELOG.md index 00714be..463d136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [0.0.1 - 2024-06-26] +## [0.0.1 - 2024-07-01] ### Added - First version of the project - Spring Application @@ -136,3 +136,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Check export execution status - Coder Service - RStudio in Coder +- Research environment project type +- Upload file to Coder +- Delete Coder workspace job diff --git a/src/main/java/de/samply/app/ProjectManagerConst.java b/src/main/java/de/samply/app/ProjectManagerConst.java index 147a542..63cda55 100644 --- a/src/main/java/de/samply/app/ProjectManagerConst.java +++ b/src/main/java/de/samply/app/ProjectManagerConst.java @@ -89,6 +89,9 @@ public class ProjectManagerConst { public final static String FETCH_OTHER_DOCUMENTS_ACTION = "FETCH_OTHER_DOCUMENTS"; public final static String ACCEPT_PROJECT_RESULTS_ACTION = "ACCEPT_PROJECT_RESULTS"; public final static String REJECT_PROJECT_RESULTS_ACTION = "REJECT_PROJECT_RESULTS"; + public final static String ACCEPT_PROJECT_ANALYSIS_ACTION = "ACCEPT_PROJECT_ANALYSIS_ACTION"; + public final static String REJECT_PROJECT_ANALYSIS_ACTION = "REJECT_PROJECT_ANALYSIS_ACTION"; + public final static String REQUEST_CHANGES_IN_PROJECT_ANALYSIS_ACTION = "REQUEST_CHANGES_IN_PROJECT_ANALYSIS_ACTION"; public final static String REQUEST_CHANGES_IN_PROJECT_ACTION = "REQUEST_CHANGES_IN_PROJECT"; public final static String FETCH_NOTIFICATIONS_ACTION = "FETCH_NOTIFICATIONS"; public final static String SET_NOTIFICATION_AS_READ_ACTION = "SET_NOTIFICATION_AS_READ"; @@ -137,6 +140,9 @@ public class ProjectManagerConst { public final static String ACCEPT_PROJECT_RESULTS = "/accept-project-results"; public final static String REJECT_PROJECT_RESULTS = "/reject-project-results"; public final static String REQUEST_CHANGES_IN_PROJECT = "/request-changes-in-project"; + public final static String ACCEPT_PROJECT_ANALYSIS = "/accept-project-analysis"; + public final static String REJECT_PROJECT_ANALYSIS = "/reject-project-analysis"; + public final static String REQUEST_CHANGES_IN_PROJECT_ANALYSIS = "/request-changes-in-project-analysis"; public final static String FETCH_PROJECT_BRIDGEHEADS = "/project-bridgeheads"; public final static String FETCH_VISIBLE_PROJECT_BRIDGEHEADS = "/visible-project-bridgeheads"; public final static String FETCH_PROJECT = "/project"; @@ -242,14 +248,15 @@ public class ProjectManagerConst { public static final String EXPORTER_QUERY_CONTEXT_PROJECT_ID = "PROJECT-ID"; public final static String EXPORTER_QUERY_CONTEXT_SEPARATOR = ";"; public final static String API_KEY = "ApiKey"; + public final static String EXPORTER_FETCH_QUERY_EXECUTION_URL_PATH = "/response?query-execution-id="; - // Focus - public final static String FOCUS_METADATA_PROJECT = "exporter"; - public final static String FOCUS_TASK_PATH = "/v1/tasks"; - public final static String FOCUS_TASK_RESULTS_PATH = "/results"; - public final static String FOCUS_TASK_WAIT_TIME_PARAM = "wait_time"; - public final static String FOCUS_TASK_WAIT_COUNT_PARAM = "wait_count"; + // Beam + public final static String BEAM_FOCUS_METADATA_PROJECT = "exporter"; + public final static String BEAM_TASK_PATH = "/v1/tasks"; + public final static String BEAM_TASK_RESULTS_PATH = "/results"; + public final static String BEAM_TASK_WAIT_TIME_PARAM = "wait_time"; + public final static String BEAM_TASK_WAIT_COUNT_PARAM = "wait_count"; // Token Manager Variables public final static String TOKEN_MANAGER_ROOT = "/api"; @@ -306,12 +313,13 @@ public class ProjectManagerConst { public final static String EMAIL_TEMPLATES_DIRECTORY = "EMAIL_TEMPLATES_DIRECTORY"; public final static String EXPORT_TEMPLATES = "EXPORT_TEMPLATES"; public final static String DATASHIELD_TEMPLATES = "DATASHIELD_TEMPLATES"; - public final static String FOCUS_PROJECT_MANAGER_ID = "FOCUS_PROJECT_MANAGER_ID"; - public final static String FOCUS_TTL = "FOCUS_TTL"; - public final static String FOCUS_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS = "FOCUS_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS"; - public final static String FOCUS_FAILURE_STRATEGY_MAX_TRIES = "FOCUS_FAILURE_STRATEGY_MAX_TRIES"; - public final static String FOCUS_URL = "FOCUS_URL"; - public final static String FOCUS_API_KEY = "FOCUS_API_KEY"; + public final static String RESEARCH_ENVIRONMENT_TEMPLATES = "RESEARCH_ENVIRONMENT_TEMPLATES"; + public final static String BEAM_PROJECT_MANAGER_ID = "BEAM_PROJECT_MANAGER_ID"; + public final static String BEAM_TTL = "BEAM_TTL"; + public final static String BEAM_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS = "BEAM_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS"; + public final static String BEAM_FAILURE_STRATEGY_MAX_TRIES = "BEAM_FAILURE_STRATEGY_MAX_TRIES"; + public final static String BEAM_URL = "BEAM_URL"; + public final static String EXPORTER_API_KEY = "EXPORTER_API_KEY"; public final static String TOKEN_MANAGER_URL = "TOKEN_MANAGER_URL"; public final static String ENABLE_EMAILS = "ENABLE_EMAILS"; public final static String MANAGE_TOKENS_CRON_EXPRESSION = "MANAGE_TOKENS_CRON_EXPRESSION"; @@ -339,12 +347,15 @@ public class ProjectManagerConst { public final static String CODER_CREATE_PATH = "CODER_CREATE_PATH"; public final static String CODER_DELETE_PATH = "CODER_DELETE_PATH"; public final static String CODER_SESSION_TOKEN = "CODER_SESSION_TOKEN"; + public final static String CODER_CRON_EXPRESSION = "CODER_CRON_EXPRESSION"; public final static String CODER_ENABLE_JUPYTER_LAB_PARAM_VALUE = "CODER_ENABLE_JUPYTER_LAB_PARAM_VALUE"; public final static String CODER_ENABLE_RSTUDIO_PARAM_VALUE = "CODER_ENABLE_RSTUDIO_PARAM_VALUE"; public final static String CODER_ENABLE_VS_CODE_SERVER_PARAM_VALUE = "CODER_ENABLE_VS_CODE_SERVER_PARAM_VALUE"; public final static String CODER_DOTFILES_URL_PARAM_VALUE = "CODER_DOTFILES_URL_PARAM_VALUE"; public final static String CODER_ENABLE_FILE_RECEIVER_PARAM_VALUE = "CODER_ENABLE_FILE_RECEIVER_PARAM_VALUE"; + public final static String CODER_BEAM_ID_SUFFIX = "CODER_BEAM_ID_SUFFIX"; + public final static String CODER_TEST_FILE_BEAM_ID = "CODER_TEST_FILE_BEAM_ID"; public final static String ENABLE_CODER = "ENABLE_CODER"; // Spring Values (SV) @@ -388,14 +399,15 @@ public class ProjectManagerConst { public final static String EMAIL_TEMPLATES_DIRECTORY_SV = HEAD_SV + EMAIL_TEMPLATES_DIRECTORY + BOTTOM_SV; public final static String EXPORT_TEMPLATES_SV = HEAD_SV + EXPORT_TEMPLATES + BOTTOM_SV; public final static String DATASHIELD_TEMPLATES_SV = HEAD_SV + DATASHIELD_TEMPLATES + BOTTOM_SV; + public final static String RESEARCH_ENVIRONMENT_TEMPLATES_SV = HEAD_SV + RESEARCH_ENVIRONMENT_TEMPLATES + BOTTOM_SV; public final static String TOKEN_MANAGER_URL_SV = HEAD_SV + TOKEN_MANAGER_URL + BOTTOM_SV; - public final static String FOCUS_PROJECT_MANAGER_ID_SV = HEAD_SV + FOCUS_PROJECT_MANAGER_ID + BOTTOM_SV; - public final static String FOCUS_TTL_SV = HEAD_SV + FOCUS_TTL + ":60s" + BOTTOM_SV; - public final static String FOCUS_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS_SV = - HEAD_SV + FOCUS_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS + ":1000" + BOTTOM_SV; - public final static String FOCUS_FAILURE_STRATEGY_MAX_TRIES_SV = HEAD_SV + FOCUS_FAILURE_STRATEGY_MAX_TRIES + ":5" + BOTTOM_SV; - public final static String FOCUS_URL_SV = HEAD_SV + FOCUS_URL + BOTTOM_SV; - public final static String FOCUS_API_KEY_SV = HEAD_SV + FOCUS_API_KEY + BOTTOM_SV; + public final static String BEAM_PROJECT_MANAGER_ID_SV = HEAD_SV + BEAM_PROJECT_MANAGER_ID + BOTTOM_SV; + public final static String BEAM_TTL_SV = HEAD_SV + BEAM_TTL + ":60s" + BOTTOM_SV; + public final static String BEAM_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS_SV = + HEAD_SV + BEAM_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS + ":1000" + BOTTOM_SV; + public final static String BEAM_FAILURE_STRATEGY_MAX_TRIES_SV = HEAD_SV + BEAM_FAILURE_STRATEGY_MAX_TRIES + ":5" + BOTTOM_SV; + public final static String BEAM_URL_SV = HEAD_SV + BEAM_URL + BOTTOM_SV; + public final static String EXPORTER_API_KEY_SV = HEAD_SV + EXPORTER_API_KEY + BOTTOM_SV; public final static String ENABLE_EMAILS_SV = HEAD_SV + ENABLE_EMAILS + ":true" + BOTTOM_SV; public final static String ENABLE_TOKEN_MANAGER_SV = HEAD_SV + ENABLE_TOKEN_MANAGER + ":true" + BOTTOM_SV; public final static String ENABLE_EXPORTER_SV = HEAD_SV + ENABLE_EXPORTER + ":true" + BOTTOM_SV; @@ -422,6 +434,9 @@ public class ProjectManagerConst { public final static String CODER_CREATE_PATH_SV = HEAD_SV + CODER_CREATE_PATH + BOTTOM_SV; public final static String CODER_DELETE_PATH_SV = HEAD_SV + CODER_DELETE_PATH + BOTTOM_SV; public final static String CODER_SESSION_TOKEN_SV = HEAD_SV + CODER_SESSION_TOKEN + BOTTOM_SV; + public final static String CODER_BEAM_ID_SUFFIX_SV = HEAD_SV + CODER_BEAM_ID_SUFFIX + BOTTOM_SV; + public final static String CODER_TEST_FILE_BEAM_ID_SV = HEAD_SV + CODER_TEST_FILE_BEAM_ID + BOTTOM_SV; + public final static String CODER_CRON_EXPRESSION_SV = HEAD_SV + CODER_CRON_EXPRESSION + BOTTOM_SV; public final static String CODER_ENABLE_JUPYTER_LAB_PARAM_VALUE_SV = HEAD_SV + CODER_ENABLE_JUPYTER_LAB_PARAM_VALUE + ":1" + BOTTOM_SV; public final static String CODER_ENABLE_RSTUDIO_PARAM_VALUE_SV = HEAD_SV + CODER_ENABLE_RSTUDIO_PARAM_VALUE + ":1" + BOTTOM_SV; diff --git a/src/main/java/de/samply/app/ProjectManagerController.java b/src/main/java/de/samply/app/ProjectManagerController.java index 46336e0..891f3a7 100644 --- a/src/main/java/de/samply/app/ProjectManagerController.java +++ b/src/main/java/de/samply/app/ProjectManagerController.java @@ -181,7 +181,7 @@ public ResponseEntity fetchProjectsBridgeheads( @RoleConstraints(projectRoles = {ProjectRole.CREATOR, ProjectRole.PROJECT_MANAGER_ADMIN}) @StateConstraints(projectStates = {ProjectState.DEVELOP}) - @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD}) + @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD, ProjectType.RESEARCH_ENVIRONMENT}) @EmailSender(templateType = EmailTemplateType.INVITATION, recipients = {EmailRecipientType.EMAIL_ANNOTATION}) @EmailSender(templateType = EmailTemplateType.REQUEST_TECHNICAL_APPROVAL, recipients = {EmailRecipientType.BRIDGEHEAD_ADMIN}) //TODO: Send email to PM-ADMIN, that there was a problem with the operation @@ -199,7 +199,7 @@ public ResponseEntity setUserAsDeveloper( @RoleConstraints(organisationRoles = {OrganisationRole.PROJECT_MANAGER_ADMIN}) @StateConstraints(projectStates = {ProjectState.PILOT}) - @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD}) + @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD, ProjectType.RESEARCH_ENVIRONMENT}) @EmailSender(templateType = EmailTemplateType.INVITATION, recipients = {EmailRecipientType.EMAIL_ANNOTATION}) @EmailSender(templateType = EmailTemplateType.REQUEST_TECHNICAL_APPROVAL, recipients = {EmailRecipientType.BRIDGEHEAD_ADMIN}) @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.USER_MODULE) @@ -431,7 +431,7 @@ public ResponseEntity createProject( @RoleConstraints(organisationRoles = {OrganisationRole.PROJECT_MANAGER_ADMIN}) @StateConstraints(projectStates = {ProjectState.ACCEPTED}) - @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD}) + @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD, ProjectType.RESEARCH_ENVIRONMENT}) @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) @FrontendAction(action = ProjectManagerConst.START_DEVELOP_STAGE_ACTION) @PostMapping(value = ProjectManagerConst.START_DEVELOP_STAGE) @@ -443,7 +443,7 @@ public ResponseEntity startDevelopStage( @RoleConstraints(organisationRoles = {OrganisationRole.PROJECT_MANAGER_ADMIN}) @StateConstraints(projectStates = {ProjectState.DEVELOP}) - @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD}) + @ProjectConstraints(projectTypes = {ProjectType.DATASHIELD, ProjectType.RESEARCH_ENVIRONMENT}) @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) @FrontendAction(action = ProjectManagerConst.START_PILOT_STAGE_ACTION) @PostMapping(value = ProjectManagerConst.START_PILOT_STAGE) @@ -608,6 +608,49 @@ public ResponseEntity requestChangesInProject( return convertToResponseEntity(() -> userService.requestChangesInProject(projectCode, bridgehead)); } + @RoleConstraints(projectRoles = {ProjectRole.DEVELOPER, ProjectRole.PILOT}) + @StateConstraints(projectStates = {ProjectState.DEVELOP, ProjectState.PILOT}) + @EmailSender(templateType = EmailTemplateType.ANALYSIS_ACCEPTED, recipients = {EmailRecipientType.PROJECT_MANAGER_ADMIN, EmailRecipientType.ALL_DEVELOPERS, EmailRecipientType.ALL_PILOTS, EmailRecipientType.ALL_FINALS}) + @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) + @FrontendAction(action = ProjectManagerConst.ACCEPT_PROJECT_ANALYSIS_ACTION) + @PostMapping(value = ProjectManagerConst.ACCEPT_PROJECT_ANALYSIS) + public ResponseEntity acceptProjectAnalysis( + @ProjectCode @RequestParam(name = ProjectManagerConst.PROJECT_CODE) String projectCode, + @Bridgehead @RequestParam(name = ProjectManagerConst.BRIDGEHEAD) String bridgehead + ) { + return convertToResponseEntity(() -> userService.acceptProject(projectCode, bridgehead)); + } + + @RoleConstraints(projectRoles = {ProjectRole.DEVELOPER, ProjectRole.PILOT}) + @StateConstraints(projectStates = {ProjectState.DEVELOP, ProjectState.PILOT}) + @EmailSender(templateType = EmailTemplateType.ANALYSIS_REJECTED, recipients = {EmailRecipientType.PROJECT_MANAGER_ADMIN, EmailRecipientType.ALL_DEVELOPERS, EmailRecipientType.ALL_PILOTS, EmailRecipientType.ALL_FINALS}) + @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) + @FrontendAction(action = ProjectManagerConst.REJECT_PROJECT_ANALYSIS_ACTION) + @PostMapping(value = ProjectManagerConst.REJECT_PROJECT_ANALYSIS) + public ResponseEntity rejectProjectAnalysis( + @ProjectCode @RequestParam(name = ProjectManagerConst.PROJECT_CODE) String projectCode, + @Bridgehead @RequestParam(name = ProjectManagerConst.BRIDGEHEAD) String bridgehead, + // Message is sent per email + @Message @RequestParam(name = ProjectManagerConst.MESSAGE, required = false) String message + ) { + return convertToResponseEntity(() -> userService.rejectProject(projectCode, bridgehead)); + } + + @RoleConstraints(projectRoles = {ProjectRole.DEVELOPER, ProjectRole.PILOT}) + @StateConstraints(projectStates = {ProjectState.DEVELOP, ProjectState.PILOT}) + @EmailSender(templateType = EmailTemplateType.REQUEST_CHANGES_IN_PROJECT_ANALYSIS, recipients = {EmailRecipientType.PROJECT_MANAGER_ADMIN, EmailRecipientType.ALL_DEVELOPERS, EmailRecipientType.ALL_PILOTS, EmailRecipientType.ALL_FINALS}) + @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) + @FrontendAction(action = ProjectManagerConst.REQUEST_CHANGES_IN_PROJECT_ANALYSIS_ACTION) + @PostMapping(value = ProjectManagerConst.REQUEST_CHANGES_IN_PROJECT_ANALYSIS) + public ResponseEntity requestChangesInProjectAnalysis( + @ProjectCode @RequestParam(name = ProjectManagerConst.PROJECT_CODE) String projectCode, + @Bridgehead @RequestParam(name = ProjectManagerConst.BRIDGEHEAD) String bridgehead, + // Message is sent per email + @Message @RequestParam(name = ProjectManagerConst.MESSAGE, required = false) String message + ) { + return convertToResponseEntity(() -> userService.requestChangesInProject(projectCode, bridgehead)); + } + @RoleConstraints(organisationRoles = {OrganisationRole.PROJECT_MANAGER_ADMIN}) @StateConstraints(projectStates = {ProjectState.CREATED, ProjectState.ACCEPTED, ProjectState.DEVELOP, ProjectState.PILOT, ProjectState.FINAL}) @FrontendSiteModule(site = ProjectManagerConst.PROJECT_VIEW_SITE, module = ProjectManagerConst.PROJECT_STATE_MODULE) diff --git a/src/main/java/de/samply/bridgehead/BridgeheadConfiguration.java b/src/main/java/de/samply/bridgehead/BridgeheadConfiguration.java index e72a522..82aac05 100644 --- a/src/main/java/de/samply/bridgehead/BridgeheadConfiguration.java +++ b/src/main/java/de/samply/bridgehead/BridgeheadConfiguration.java @@ -25,7 +25,8 @@ public class BridgeheadConfiguration { @Data public static class BridgeheadConfig { private String explorerId; - private String focusId; + private String focusBeamId; + private String fileDispatcherBeamId; private String tokenManagerId; private String humanReadable; } @@ -34,7 +35,7 @@ public static class BridgeheadConfig { private void initIdBridgeheadMaps() { config.forEach((bridgehead, bridgeheadConfig) -> { addBridgeheadId(bridgehead, bridgeheadConfig.getExplorerId(), explorerIdBridgeheadMap); - addBridgeheadId(bridgehead, bridgeheadConfig.getFocusId(), focusIdBridgeheadMap); + addBridgeheadId(bridgehead, bridgeheadConfig.getFocusBeamId(), focusIdBridgeheadMap); addBridgeheadId(bridgehead, bridgeheadConfig.getTokenManagerId(), tokenManagerIdBridgeheadMap); }); } @@ -57,8 +58,8 @@ public boolean isRegisteredBridgehead(String bridgehead) { return config.keySet().contains(bridgehead); } - public String getFocusId(String bridgehead) { - return config.get(bridgehead).getFocusId(); + public String getFocusBeamId(String bridgehead) { + return config.get(bridgehead).getFocusBeamId(); } public String getHumanReadable(String bridgehead) { @@ -86,6 +87,10 @@ public String fetchBridgeheadForFocusId(String focusId) { return fetchBridgehead(focusId, focusIdBridgeheadMap); } + public String getFileDispatcherBeamId(String bridgehead) { + return config.get(bridgehead).getFileDispatcherBeamId(); + } + public Optional getBridgeheadForTokenManagerId(String tokenManagerId) { return fetchBridgeheadOptional(tokenManagerId, tokenManagerIdBridgeheadMap); } diff --git a/src/main/java/de/samply/coder/CoderJob.java b/src/main/java/de/samply/coder/CoderJob.java new file mode 100644 index 0000000..809d923 --- /dev/null +++ b/src/main/java/de/samply/coder/CoderJob.java @@ -0,0 +1,79 @@ +package de.samply.coder; + +import de.samply.app.ProjectManagerConst; +import de.samply.db.model.ProjectBridgeheadUser; +import de.samply.db.model.ProjectCoder; +import de.samply.db.repository.ProjectBridgeheadUserRepository; +import de.samply.db.repository.ProjectCoderRepository; +import de.samply.exporter.ExporterService; +import de.samply.project.ProjectType; +import de.samply.project.state.ProjectBridgeheadState; +import de.samply.query.QueryState; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Component +public class CoderJob { + + private final ProjectBridgeheadUserRepository projectBridgeheadUserRepository; + private final ProjectCoderRepository projectCoderRepository; + private final CoderService coderService; + private final ExporterService exporterService; + + public CoderJob(ProjectBridgeheadUserRepository projectBridgeheadUserRepository, + ProjectCoderRepository projectCoderRepository, + CoderService coderService, + ExporterService exporterService + ) { + this.projectBridgeheadUserRepository = projectBridgeheadUserRepository; + this.projectCoderRepository = projectCoderRepository; + this.coderService = coderService; + this.exporterService = exporterService; + } + + + @Scheduled(cron = ProjectManagerConst.CODER_CRON_EXPRESSION_SV) + public void manageCoderWorkspaces() { + manageCoderActiveUsers(); + manageCoderInactiveUsers(); + } + + public void manageCoderActiveUsers() { + fetchActiveUsers().stream().forEach(user -> { + Optional projectCoder = projectCoderRepository.findByProjectBridgeheadUserAndDeletedAtIsNull(user); + if (projectCoder.isEmpty()) { + this.coderService.createWorkspace(user); + } else if (!projectCoder.get().isExportTransferred()) { + exporterService.transferFileToCoder(user.getProjectBridgehead(), projectCoder.get()).subscribe(result -> { + projectCoder.get().setExportTransferred(true); + this.projectCoderRepository.save(projectCoder.get()); + }); + } + }); + } + + private List fetchActiveUsers() { + return projectBridgeheadUserRepository.getDistinctInValidaProjectStateByProjectTypeAndQueryStateAndProjectBridgeheadState(ProjectType.RESEARCH_ENVIRONMENT, QueryState.FINISHED, ProjectBridgeheadState.ACCEPTED); + } + + public void manageCoderInactiveUsers() { + fetchInactiveUsers().stream().forEach(user -> { + Optional projectCoder = projectCoderRepository.findByProjectBridgeheadUserAndDeletedAtIsNull(user); + if (projectCoder.isPresent()) { + this.coderService.deleteWorkspace(user); + projectCoder.get().setDeletedAt(Instant.now()); + projectCoderRepository.save(projectCoder.get()); + } + }); + } + + private List fetchInactiveUsers() { + return projectBridgeheadUserRepository.getDistinctInInvalidProjectStateByProjectType(ProjectType.RESEARCH_ENVIRONMENT); + } + +} diff --git a/src/main/java/de/samply/coder/CoderService.java b/src/main/java/de/samply/coder/CoderService.java index f6c10f3..25b12be 100644 --- a/src/main/java/de/samply/coder/CoderService.java +++ b/src/main/java/de/samply/coder/CoderService.java @@ -5,10 +5,10 @@ import de.samply.coder.request.CreateRequestParameter; import de.samply.coder.request.Response; import de.samply.coder.request.TransitionRequestBody; -import de.samply.db.model.Project; +import de.samply.db.model.ProjectBridgeheadUser; import de.samply.db.model.ProjectCoder; +import de.samply.db.repository.ProjectBridgeheadUserRepository; import de.samply.db.repository.ProjectCoderRepository; -import de.samply.db.repository.ProjectRepository; import de.samply.notification.NotificationService; import de.samply.notification.OperationType; import de.samply.utils.WebClientFactory; @@ -33,7 +33,7 @@ public class CoderService { private final boolean coderEnabled; - private final ProjectRepository projectRepository; + private final ProjectBridgeheadUserRepository projectBridgeheadUserRepository; private final ProjectCoderRepository projectCoderRepository; private final NotificationService notificationService; private final String coderTemplateVersionId; @@ -51,7 +51,7 @@ public class CoderService { public CoderService( ProjectCoderRepository projectCoderRepository, NotificationService notificationService, - ProjectRepository projectRepository, + ProjectBridgeheadUserRepository projectBridgeheadUserRepository, @Value(ProjectManagerConst.ENABLE_CODER_SV) boolean coderEnabled, @Value(ProjectManagerConst.CODER_BASE_URL_SV) String coderBaseUrl, @Value(ProjectManagerConst.CODER_ORGANISATION_ID_SV) String coderOrganizationId, @@ -68,7 +68,7 @@ public CoderService( this.coderEnabled = coderEnabled; this.projectCoderRepository = projectCoderRepository; this.notificationService = notificationService; - this.projectRepository = projectRepository; + this.projectBridgeheadUserRepository = projectBridgeheadUserRepository; this.enableJupyterLab = enableJupyterLab; this.enableRstudio = enableRstudio; this.enableVsCodeServer = enableVsCodeServer; @@ -98,21 +98,23 @@ private String fetchVariableExpresion(String variable) { } public void createWorkspace(String email, String projectCode) throws CoderServiceException { - Optional project = projectRepository.findByCode(projectCode); - if (project.isEmpty()) { - throw new CoderServiceException("Project " + projectCode + " not found"); + Optional user = projectBridgeheadUserRepository.getFirstByEmailAndProjectBridgehead_ProjectCodeOrderByModifiedAtDesc(email, projectCode); + if (user.isEmpty()) { + throw new CoderServiceException("User " + email + " for project " + projectCode + " not found"); } - createWorkspace(email, project.get()); + createWorkspace(user.get()); } - public void createWorkspace(@NotNull String email, @NotNull Project project) { + public void createWorkspace(@NotNull ProjectBridgeheadUser projectBridgeheadUser) { if (coderEnabled) { - ProjectCoder projectCoder = generateProjectCoder(email, project); + ProjectCoder projectCoder = generateProjectCoder(projectBridgeheadUser); CreateRequestBody createRequestBody = generateCreateRequestBody(projectCoder); Response response = createWorkspace(projectCoder, createRequestBody).block(); projectCoder.setWorkspaceId(response.getLatestBuild().getWorkspaceId()); projectCoderRepository.save(projectCoder); - notificationService.createNotification(project.getCode(), null, email, OperationType.CREATE_CODER_WORKSPACE, + notificationService.createNotification(projectBridgeheadUser.getProjectBridgehead().getProject().getCode(), + projectBridgeheadUser.getProjectBridgehead().getBridgehead(), projectBridgeheadUser.getEmail(), + OperationType.CREATE_CODER_WORKSPACE, "Created workspace " + projectCoder.getWorkspaceId(), null, null); } @@ -121,7 +123,8 @@ public void createWorkspace(@NotNull String email, @NotNull Project project) { private Mono createWorkspace(ProjectCoder projectCoder, CreateRequestBody createRequestBody) { return this.webClient.post() .uri(uriBuilder -> uriBuilder.path(ProjectManagerConst.CODER_API_PATH).path( - replaceVariablesInPath(coderCreatePath, Map.of(ProjectManagerConst.CODER_MEMBER_ID, fetchCoderMemberId(projectCoder.getEmail())))).build()) + replaceVariablesInPath(coderCreatePath, Map.of(ProjectManagerConst.CODER_MEMBER_ID, + fetchCoderMemberId(projectCoder.getProjectBridgeheadUser().getEmail())))).build()) .header(ProjectManagerConst.CODER_SESSION_TOKEN_HEADER, coderSessionToken) .contentType(MediaType.APPLICATION_JSON) .bodyValue(createRequestBody) @@ -130,7 +133,8 @@ private Mono createWorkspace(ProjectCoder projectCoder, CreateRequestB return clientResponse.bodyToMono(Response.class); } else { log.error("Http error " + clientResponse.statusCode() + " creating workspace in Coder for user " - + projectCoder.getEmail() + " in project " + projectCoder.getProject().getCode()); + + projectCoder.getProjectBridgeheadUser().getEmail() + " in project " + + projectCoder.getProjectBridgeheadUser().getProjectBridgehead().getProject().getCode()); return clientResponse.bodyToMono(String.class).flatMap(errorBody -> { log.error("Error: {}", errorBody); return Mono.error(new RuntimeException(errorBody)); @@ -139,31 +143,31 @@ private Mono createWorkspace(ProjectCoder projectCoder, CreateRequestB }); } - private ProjectCoder generateProjectCoder(String email, Project project) { + private ProjectCoder generateProjectCoder(ProjectBridgeheadUser projectBridgeheadUser) { ProjectCoder projectCoder = new ProjectCoder(); - projectCoder.setProject(project); - projectCoder.setEmail(email); - projectCoder.setAppId(fetchCoderAppId(email, project)); + projectCoder.setProjectBridgeheadUser(projectBridgeheadUser); + projectCoder.setAppId(fetchCoderAppId(projectBridgeheadUser)); projectCoder.setAppSecret(generateAppSecret()); return projectCoder; } public void deleteWorkspace(@NotNull String email, @NotNull String projectCode) throws CoderServiceException { - Optional project = projectRepository.findByCode(projectCode); - if (project.isEmpty()) { - throw new CoderServiceException("Project " + projectCode + " not found"); + Optional user = projectBridgeheadUserRepository.getFirstByEmailAndProjectBridgehead_ProjectCodeOrderByModifiedAtDesc(email, projectCode); + if (user.isEmpty()) { + throw new CoderServiceException("User " + email + " for project " + projectCode + " not found"); } - deleteWorkspace(email, project.get()); + deleteWorkspace(user.get()); } - public void deleteWorkspace(@NotNull String email, @NotNull Project project) { + public void deleteWorkspace(@NotNull ProjectBridgeheadUser user) { if (coderEnabled) { - projectCoderRepository.findByProjectAndEmail(project, email) + projectCoderRepository.findByProjectBridgeheadUser(user) .filter(projectCoder -> projectCoder.getDeletedAt() == null).ifPresent(projectCoder -> { deleteWorkspace(projectCoder).block(); projectCoder.setDeletedAt(Instant.now()); projectCoderRepository.save(projectCoder); - notificationService.createNotification(project.getCode(), null, email, OperationType.DELETE_CODER_WORKSPACE, + notificationService.createNotification(user.getProjectBridgehead().getProject().getCode(), + user.getProjectBridgehead().getBridgehead(), user.getEmail(), OperationType.DELETE_CODER_WORKSPACE, "Deleted workspace " + projectCoder.getWorkspaceId(), null, null); }); } @@ -181,7 +185,8 @@ private Mono deleteWorkspace(ProjectCoder projectCoder) { return clientResponse.bodyToMono(Response.class); } else { log.error("Http error " + clientResponse.statusCode() + " deleting workspace in Coder for user " - + projectCoder.getEmail() + " in project " + projectCoder.getProject().getCode()); + + projectCoder.getProjectBridgeheadUser().getEmail() + " in project " + + projectCoder.getProjectBridgeheadUser().getProjectBridgehead().getProject().getCode()); return clientResponse.bodyToMono(String.class).flatMap(errorBody -> { log.error("Error: {}", errorBody); return Mono.error(new RuntimeException(errorBody)); @@ -211,8 +216,9 @@ private void addRichParameterValues(CreateRequestBody createRequestBody, Project createRequestBody.setRichParameterValues(createRequestParameters.toArray(CreateRequestParameter[]::new)); } - public String fetchCoderAppId(@NotNull String email, @NotNull Project project) { - return email.substring(0, email.indexOf("@")).replace(".", "-") + "-" + project.getCode(); + public String fetchCoderAppId(@NotNull ProjectBridgeheadUser projectBridgeheadUser) { + return projectBridgeheadUser.getEmail().substring(0, projectBridgeheadUser.getEmail().indexOf("@")) + .replace(".", "-") + "-" + projectBridgeheadUser.getProjectBridgehead().getProject().getCode(); } private String generateAppSecret() { diff --git a/src/main/java/de/samply/db/model/ProjectCoder.java b/src/main/java/de/samply/db/model/ProjectCoder.java index 5379ecd..c9cdb91 100644 --- a/src/main/java/de/samply/db/model/ProjectCoder.java +++ b/src/main/java/de/samply/db/model/ProjectCoder.java @@ -25,12 +25,12 @@ public class ProjectCoder { @Column(name = "app_secret", nullable = false) private String appSecret; - @Column(name = "email", nullable = false) - private String email; - @ManyToOne - @JoinColumn(name = "project_id", nullable = false) - private Project project; + @JoinColumn(name = "project_bridgehead_user_id", nullable = false) + private ProjectBridgeheadUser projectBridgeheadUser; + + @Column(name = "export_transferred") + private boolean isExportTransferred = false; @Column(name = "workspace_id") private String workspaceId; diff --git a/src/main/java/de/samply/db/repository/ProjectBridgeheadUserRepository.java b/src/main/java/de/samply/db/repository/ProjectBridgeheadUserRepository.java index 0812e95..80658bd 100644 --- a/src/main/java/de/samply/db/repository/ProjectBridgeheadUserRepository.java +++ b/src/main/java/de/samply/db/repository/ProjectBridgeheadUserRepository.java @@ -6,6 +6,7 @@ import de.samply.project.ProjectType; import de.samply.project.state.ProjectBridgeheadState; import de.samply.project.state.ProjectState; +import de.samply.query.QueryState; import de.samply.user.roles.ProjectRole; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -24,6 +25,8 @@ public interface ProjectBridgeheadUserRepository extends JpaRepository getFirstByEmailAndProjectBridgeheadOrderByModifiedAtDesc(String email, ProjectBridgehead projectBridgehead); + Optional getFirstByEmailAndProjectBridgehead_ProjectCodeOrderByModifiedAtDesc(String email, String projectCode); + Optional getFirstByEmailAndProjectBridgehead_ProjectAndProjectBridgehead_BridgeheadOrderByModifiedAtDesc(String email, Project project, String bridgehead); List getByProjectBridgehead(ProjectBridgehead projectBridgehead); @@ -50,4 +53,18 @@ public interface ProjectBridgeheadUserRepository extends JpaRepository getDistinctByProjectRoleAndProjectCode(ProjectRole projectRole, String projectCode); + @Query("SELECT DISTINCT pbu FROM ProjectBridgeheadUser pbu WHERE pbu.projectBridgehead.project.type = :projectType AND " + + "pbu.projectBridgehead.state = :projectBridgeheadState AND pbu.projectBridgehead.queryState = :queryState AND (" + + "(pbu.projectBridgehead.project.state = 'DEVELOP' AND pbu.projectRole = 'DEVELOPER') OR " + + "(pbu.projectBridgehead.project.state = 'PILOT' AND pbu.projectRole = 'PILOT') OR" + + "(pbu.projectBridgehead.project.state = 'FINAL' AND pbu.projectRole = 'FINAL'))") + List getDistinctInValidaProjectStateByProjectTypeAndQueryStateAndProjectBridgeheadState(ProjectType projectType, QueryState queryState, ProjectBridgeheadState projectBridgeheadState); + + @Query("SELECT DISTINCT pbu FROM ProjectBridgeheadUser pbu WHERE pbu.projectBridgehead.project.type = :projectType AND (" + + "(pbu.projectRole = 'DEVELOPER' AND pbu.projectBridgehead.project.state != 'DEVELOP') OR " + + "(pbu.projectRole = 'PILOT' AND pbu.projectBridgehead.project.state != 'PILOT') OR" + + "(pbu.projectRole = 'FINAL' AND pbu.projectBridgehead.project.state != 'FINAL'))") + List getDistinctInInvalidProjectStateByProjectType(ProjectType projectType); + + } diff --git a/src/main/java/de/samply/db/repository/ProjectCoderRepository.java b/src/main/java/de/samply/db/repository/ProjectCoderRepository.java index 44d0966..3bce822 100644 --- a/src/main/java/de/samply/db/repository/ProjectCoderRepository.java +++ b/src/main/java/de/samply/db/repository/ProjectCoderRepository.java @@ -1,6 +1,6 @@ package de.samply.db.repository; -import de.samply.db.model.Project; +import de.samply.db.model.ProjectBridgeheadUser; import de.samply.db.model.ProjectCoder; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -10,6 +10,8 @@ @Repository public interface ProjectCoderRepository extends JpaRepository { - Optional findByProjectAndEmail (Project project, String email); + Optional findByProjectBridgeheadUser(ProjectBridgeheadUser projectBridgeheadUser); + + Optional findByProjectBridgeheadUserAndDeletedAtIsNull(ProjectBridgeheadUser projectBridgeheadUser); } diff --git a/src/main/java/de/samply/email/EmailTemplateType.java b/src/main/java/de/samply/email/EmailTemplateType.java index 6d21fc6..b1b1d12 100644 --- a/src/main/java/de/samply/email/EmailTemplateType.java +++ b/src/main/java/de/samply/email/EmailTemplateType.java @@ -14,6 +14,9 @@ public enum EmailTemplateType { RESULTS_ACCEPTED, RESULTS_REJECTED, REQUEST_CHANGES_IN_PROJECT, + ANALYSIS_ACCEPTED, + ANALYSIS_REJECTED, + REQUEST_CHANGES_IN_PROJECT_ANALYSIS, NEW_PROJECT_ACCEPTED, NEW_VOTUM, REQUEST_TECHNICAL_APPROVAL, diff --git a/src/main/java/de/samply/exporter/ExporterService.java b/src/main/java/de/samply/exporter/ExporterService.java index 044cb50..e1b55dc 100644 --- a/src/main/java/de/samply/exporter/ExporterService.java +++ b/src/main/java/de/samply/exporter/ExporterService.java @@ -5,15 +5,12 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import de.samply.app.ProjectManagerConst; -import de.samply.db.model.Project; -import de.samply.db.model.ProjectBridgehead; -import de.samply.db.model.ProjectBridgeheadDataShield; -import de.samply.db.model.Query; +import de.samply.db.model.*; import de.samply.db.repository.ProjectBridgeheadDataShieldRepository; import de.samply.db.repository.ProjectBridgeheadRepository; -import de.samply.exporter.focus.FocusQuery; -import de.samply.exporter.focus.FocusService; -import de.samply.exporter.focus.FocusServiceException; +import de.samply.exporter.focus.BeamRequest; +import de.samply.exporter.focus.BeamService; +import de.samply.exporter.focus.BeamServiceException; import de.samply.exporter.focus.TaskType; import de.samply.notification.NotificationService; import de.samply.notification.OperationType; @@ -46,46 +43,55 @@ public class ExporterService { private final ProjectBridgeheadRepository projectBridgeheadRepository; - private final FocusService focusService; + private final BeamService beamService; private final WebClient webClient; private final ProjectBridgeheadDataShieldRepository projectBridgeheadDataShieldRepository; private final NotificationService notificationService; private final Set exportTemplates; private final Set datashieldTemplates; + private final Set researchEnvironmentTemplates; private final String focusProjectManagerId; - private final String focusApiKey; + private final String exporterApiKey; + private final String coderBeamIdSuffix; + private final String testCoderFileBeamId; - private final String focusWaitTime; - private final String focusWaitCount; + private final String beamWaitTime; + private final String beamWaitCount; private final int maxTimeToWaitFocusTaskInMinutes; private ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT) .registerModule(new JavaTimeModule()).configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); public ExporterService( - @Value(ProjectManagerConst.FOCUS_API_KEY_SV) String focusApiKey, - @Value(ProjectManagerConst.FOCUS_PROJECT_MANAGER_ID_SV) String focusProjectManagerId, - @Value(ProjectManagerConst.FOCUS_URL_SV) String focusUrl, + @Value(ProjectManagerConst.EXPORTER_API_KEY_SV) String exporterApiKey, + @Value(ProjectManagerConst.BEAM_PROJECT_MANAGER_ID_SV) String focusProjectManagerId, + @Value(ProjectManagerConst.BEAM_URL_SV) String focusUrl, @Value(ProjectManagerConst.EXPORT_TEMPLATES_SV) Set exportTemplates, @Value(ProjectManagerConst.DATASHIELD_TEMPLATES_SV) Set datashieldTemplates, - @Value(ProjectManagerConst.FOCUS_TTL_SV) String focusWaitTime, - @Value(ProjectManagerConst.FOCUS_FAILURE_STRATEGY_MAX_TRIES_SV) String focusWaitCount, + @Value(ProjectManagerConst.RESEARCH_ENVIRONMENT_TEMPLATES_SV) Set researchEnvironmentTemplates, + @Value(ProjectManagerConst.BEAM_TTL_SV) String beamWaitTime, + @Value(ProjectManagerConst.BEAM_FAILURE_STRATEGY_MAX_TRIES_SV) String beamWaitCount, @Value(ProjectManagerConst.MAX_TIME_TO_WAIT_FOCUS_TASK_IN_MINUTES_SV) int maxTimeToWaitFocusTaskInMinutes, - FocusService focusService, + @Value(ProjectManagerConst.CODER_BEAM_ID_SUFFIX_SV) String coderBeamIdSuffix, + @Value(ProjectManagerConst.CODER_TEST_FILE_BEAM_ID_SV) String testCoderFileBeamId, + BeamService beamService, ProjectBridgeheadDataShieldRepository projectBridgeheadDataShieldRepository, NotificationService notificationService, WebClientFactory webClientFactory, ProjectBridgeheadRepository projectBridgeheadRepository) { - this.focusService = focusService; + this.beamService = beamService; this.projectBridgeheadDataShieldRepository = projectBridgeheadDataShieldRepository; this.notificationService = notificationService; this.exportTemplates = exportTemplates; this.datashieldTemplates = datashieldTemplates; this.focusProjectManagerId = focusProjectManagerId; - this.focusWaitTime = focusWaitTime; - this.focusWaitCount = focusWaitCount; + this.beamWaitTime = beamWaitTime; + this.beamWaitCount = beamWaitCount; this.maxTimeToWaitFocusTaskInMinutes = maxTimeToWaitFocusTaskInMinutes; + this.researchEnvironmentTemplates = researchEnvironmentTemplates; + this.coderBeamIdSuffix = coderBeamIdSuffix; + this.testCoderFileBeamId = testCoderFileBeamId; this.webClient = webClientFactory.createWebClient(focusUrl); - this.focusApiKey = focusApiKey; + this.exporterApiKey = exporterApiKey; this.projectBridgeheadRepository = projectBridgeheadRepository; } @@ -107,20 +113,37 @@ public Mono checkExecutionStatus(ProjectBridgehead projec return postRequest(projectBridgehead, generateFocusBody(projectBridgehead, taskType), taskType); } - private Mono postRequest(ProjectBridgehead projectBridgehead, FocusQuery focusQuery, TaskType taskType) { + public Mono transferFileToCoder(ProjectBridgehead projectBridgehead, ProjectCoder projectCoder) { + log.info("Transfering file to Coder for project " + projectBridgehead.getProject().getCode() + " in bridgehead " + projectBridgehead.getBridgehead()); + return postRequest(projectBridgehead, generateTransferFileBeamRequest(projectBridgehead, projectCoder), TaskType.FILE_TRANSFER); + } + + private BeamRequest generateTransferFileBeamRequest(ProjectBridgehead projectBridgehead, ProjectCoder projectCoder) { + return beamService.generateExporterFileTransferBeamRequest(projectBridgehead.getBridgehead(), + projectBridgehead.getExporterExecutionId(), fetchCoderFileBeamId(projectCoder)); + } + + private String fetchCoderFileBeamId(ProjectCoder projectCoder) { + if (testCoderFileBeamId != null){ + return testCoderFileBeamId; + } + return projectCoder.getAppId() + ((coderBeamIdSuffix.startsWith(".")) ? "" : ".") + coderBeamIdSuffix; + } + + private Mono postRequest(ProjectBridgehead projectBridgehead, BeamRequest beamRequest, TaskType taskType) { return webClient.post() - .uri(uriBuilder -> uriBuilder.path(ProjectManagerConst.FOCUS_TASK_PATH).build()) + .uri(uriBuilder -> uriBuilder.path(ProjectManagerConst.BEAM_TASK_PATH).build()) .header(HttpHeaders.AUTHORIZATION, fetchAuthorization()) .contentType(MediaType.APPLICATION_JSON) - .bodyValue(focusQuery) + .bodyValue(beamRequest) .exchangeToMono(clientResponse -> { if (clientResponse.statusCode().equals(HttpStatus.OK) || clientResponse.statusCode().equals(HttpStatus.CREATED)) { fetchBridgeheadOperationType(taskType).ifPresent(operationType -> createBridgeheadNotification((HttpStatus) clientResponse.statusCode(), null, projectBridgehead, projectBridgehead.getExporterUser(), operationType)); resetProjectBridgeheadDataShield(projectBridgehead); - return Mono.just(new ExporterServiceResult(projectBridgehead, focusService.serializeFocusQuery(focusQuery))); + return Mono.just(new ExporterServiceResult(projectBridgehead, beamService.serializeFocusQuery(beamRequest))); } else { - log.error("Http Error " + clientResponse.statusCode() + " posting task " + focusQuery.getId() + " : " + focusQuery.getBody() + + log.error("Http Error " + clientResponse.statusCode() + " posting task " + beamRequest.getId() + " : " + beamRequest.getBody() + " for project " + projectBridgehead.getProject().getCode() + " and bridgehead " + projectBridgehead.getBridgehead()); return clientResponse.bodyToMono(String.class).flatMap(errorBody -> { log.error("Error: {}", errorBody); @@ -131,7 +154,7 @@ private Mono postRequest(ProjectBridgehead projectBridgeh } private String fetchAuthorization() { - return ProjectManagerConst.API_KEY + ' ' + focusProjectManagerId + ' ' + focusApiKey; + return ProjectManagerConst.API_KEY + ' ' + focusProjectManagerId + ' ' + exporterApiKey; } private void createBridgeheadNotification( @@ -144,6 +167,7 @@ private Optional fetchBridgeheadOperationType(TaskType taskType) return switch (taskType) { case CREATE -> Optional.of(OperationType.SEND_QUERY_TO_BRIDGEHEAD); case EXECUTE -> Optional.of(OperationType.SEND_QUERY_TO_BRIDGEHEAD_AND_EXECUTE); + case FILE_TRANSFER -> Optional.of(OperationType.TRANSFER_FILE_TO_CODER); default -> Optional.empty(); }; } @@ -198,21 +222,22 @@ ProjectManagerConst.EXPORTER_PARAM_QUERY_CONTEXT, generateQueryContextForExporte return convertToBase64String(result); } - private FocusQuery generateFocusBody(ProjectBridgehead projectBridgehead, TaskType taskType) throws ExporterServiceException { + private BeamRequest generateFocusBody(ProjectBridgehead projectBridgehead, TaskType taskType) throws ExporterServiceException { try { return generateFocusQueryWithoutExceptionHandling(projectBridgehead, taskType); - } catch (FocusServiceException e) { + } catch (BeamServiceException e) { throw new ExporterServiceException(e); } } - private FocusQuery generateFocusQueryWithoutExceptionHandling(ProjectBridgehead projectBridgehead, TaskType taskType) throws FocusServiceException { + private BeamRequest generateFocusQueryWithoutExceptionHandling(ProjectBridgehead projectBridgehead, TaskType taskType) throws BeamServiceException { String exporterQueryInBase64 = switch (taskType) { case CREATE -> generateExporterQueryInBase64ForExporterCreateQuery(projectBridgehead.getProject()); case EXECUTE -> generateExportQueryInBase64ForExporterRequest(projectBridgehead); case STATUS -> generateExportStatusInBase64ForExporterRequest(projectBridgehead); + default -> null; }; - return focusService.generateFocusQuery(exporterQueryInBase64, taskType, projectBridgehead.getBridgehead()); + return beamService.generateFocusBeamRequest(exporterQueryInBase64, taskType, projectBridgehead.getBridgehead()); } private String convertToString(LocalDate date) { @@ -231,6 +256,7 @@ public Set getExporterTemplates(@NotNull ProjectType projectType) { return switch (projectType) { case EXPORT -> exportTemplates; case DATASHIELD -> datashieldTemplates; + case RESEARCH_ENVIRONMENT -> researchEnvironmentTemplates; }; } @@ -250,15 +276,15 @@ private void resetProjectBridgeheadDataShield(ProjectBridgehead projectBridgehea } public Mono checkIfQueryIsAlreadySentOrExecuted(ProjectBridgehead projectBridgehead) { - Optional focusQuery = extractFocusQuery(projectBridgehead); + Optional focusQuery = extractFocusQuery(projectBridgehead); if (focusQuery.isEmpty()) { throw new RuntimeException("Focus Query not found for project " + projectBridgehead.getProject().getCode() + " and bridgehead " + projectBridgehead.getBridgehead()); } return webClient.get() .uri(uriBuilder -> uriBuilder - .path(ProjectManagerConst.FOCUS_TASK_PATH + "/" + extractTaskId(focusQuery.get()) + ProjectManagerConst.FOCUS_TASK_RESULTS_PATH) - .queryParam(ProjectManagerConst.FOCUS_TASK_WAIT_TIME_PARAM, focusWaitTime) - .queryParam(ProjectManagerConst.FOCUS_TASK_WAIT_COUNT_PARAM, focusWaitCount).build()) + .path(ProjectManagerConst.BEAM_TASK_PATH + "/" + extractTaskId(focusQuery.get()) + ProjectManagerConst.BEAM_TASK_RESULTS_PATH) + .queryParam(ProjectManagerConst.BEAM_TASK_WAIT_TIME_PARAM, beamWaitTime) + .queryParam(ProjectManagerConst.BEAM_TASK_WAIT_COUNT_PARAM, beamWaitCount).build()) .header(HttpHeaders.AUTHORIZATION, fetchAuthorization()) .exchangeToMono(clientResponse -> { if (clientResponse.statusCode().equals(HttpStatus.OK) || clientResponse.statusCode().equals(HttpStatus.PARTIAL_CONTENT)) { @@ -268,12 +294,12 @@ public Mono checkIfQueryIsAlreadySentOrExecuted(ProjectBr default -> Optional.empty(); }; operationType.ifPresent(type -> createBridgeheadNotification(HttpStatus.OK, null, projectBridgehead, projectBridgehead.getExporterUser(), type)); - return clientResponse.bodyToMono(FocusQuery[].class).filter(focusQueries -> focusQueries != null && focusQueries.length > 0).flatMap(newFocusQuery -> { + return clientResponse.bodyToMono(BeamRequest[].class).filter(focusQueries -> focusQueries != null && focusQueries.length > 0).flatMap(newBeamRequest -> { if (projectBridgehead.getQueryState() == QueryState.EXPORT_RUNNING_2) { - if (newFocusQuery[0].getBody() == null) { + if (newBeamRequest[0].getBody() == null) { return Mono.empty(); } - String decodedBody = Base64Utils.decode(newFocusQuery[0].getBody()); + String decodedBody = Base64Utils.decode(newBeamRequest[0].getBody()); if (!decodedBody.contains("OK")) { if (decodedBody.contains("ERROR")) { modifyProjectBridgeheadState(projectBridgehead, QueryState.ERROR); @@ -283,7 +309,7 @@ public Mono checkIfQueryIsAlreadySentOrExecuted(ProjectBr return Mono.empty(); } } - return Mono.just(new ExporterServiceResult(projectBridgehead, focusService.serializeFocusQuery(newFocusQuery[0]))); + return Mono.just(new ExporterServiceResult(projectBridgehead, beamService.serializeFocusQuery(newBeamRequest[0]))); }); } else { log.error("Http Error " + clientResponse.statusCode() + " checking task " + extractTaskId(focusQuery.get()) + @@ -305,13 +331,13 @@ private void modifyProjectBridgeheadState(ProjectBridgehead projectBridgehead, Q projectBridgeheadRepository.save(projectBridgehead); } - private String extractTaskId(FocusQuery focusQuery) { - return (focusQuery.getId() != null) ? focusQuery.getId() : focusQuery.getTask(); + private String extractTaskId(BeamRequest beamRequest) { + return (beamRequest.getId() != null) ? beamRequest.getId() : beamRequest.getTask(); } - private Optional extractFocusQuery(ProjectBridgehead projectBridgehead) { + private Optional extractFocusQuery(ProjectBridgehead projectBridgehead) { if (projectBridgehead.getExporterResponse() != null) { - FocusQuery[] focusQueries = focusService.deserializeFocusResponse(projectBridgehead.getExporterResponse()); + BeamRequest[] focusQueries = beamService.deserializeFocusResponse(projectBridgehead.getExporterResponse()); if (focusQueries != null && focusQueries.length > 0) { return Optional.of(focusQueries[0]); } @@ -328,7 +354,7 @@ private boolean isQueryStateToBeChangedToError(HttpStatus httpStatus, ProjectBri public Optional fetchExporterExecutionIdFromExporterResponse(String exporterResponse) { if (exporterResponse != null) { - Optional focusQuery = deserializeFocusResponse(exporterResponse); + Optional focusQuery = deserializeFocusResponse(exporterResponse); if (focusQuery.isPresent() && focusQuery.get().length > 0 && focusQuery.get()[0].getBody() != null) { return fetchQueryExecutionIdFromQueryExecutionIdUrl(Base64Utils.decode(focusQuery.get()[0].getBody())); } @@ -336,10 +362,10 @@ public Optional fetchExporterExecutionIdFromExporterResponse(String expo return Optional.empty(); } - private Optional deserializeFocusResponse(String exporterResponse) { + private Optional deserializeFocusResponse(String exporterResponse) { try { - return Optional.of(focusService.deserializeFocusResponse(exporterResponse)); - } catch (FocusServiceException e) { + return Optional.of(beamService.deserializeFocusResponse(exporterResponse)); + } catch (BeamServiceException e) { log.error(ExceptionUtils.getStackTrace(e)); return Optional.empty(); } diff --git a/src/main/java/de/samply/exporter/focus/FocusQuery.java b/src/main/java/de/samply/exporter/focus/BeamRequest.java similarity index 89% rename from src/main/java/de/samply/exporter/focus/FocusQuery.java rename to src/main/java/de/samply/exporter/focus/BeamRequest.java index c1c61e3..2808152 100644 --- a/src/main/java/de/samply/exporter/focus/FocusQuery.java +++ b/src/main/java/de/samply/exporter/focus/BeamRequest.java @@ -4,7 +4,7 @@ import lombok.Data; @Data -public class FocusQuery { +public class BeamRequest { @JsonProperty("body") private String body; @@ -15,7 +15,7 @@ public class FocusQuery { @JsonProperty("id") private String id; @JsonProperty("metadata") - private FocusQueryMetadata metadata; + private BeamRequestMetadata metadata; @JsonProperty("to") private String[] to; @JsonProperty("ttl") diff --git a/src/main/java/de/samply/exporter/focus/FocusQueryMetadata.java b/src/main/java/de/samply/exporter/focus/BeamRequestMetadata.java similarity index 87% rename from src/main/java/de/samply/exporter/focus/FocusQueryMetadata.java rename to src/main/java/de/samply/exporter/focus/BeamRequestMetadata.java index 7a45cab..1ed68dd 100644 --- a/src/main/java/de/samply/exporter/focus/FocusQueryMetadata.java +++ b/src/main/java/de/samply/exporter/focus/BeamRequestMetadata.java @@ -4,7 +4,7 @@ import lombok.Data; @Data -public class FocusQueryMetadata { +public class BeamRequestMetadata { @JsonProperty("project") private String project; diff --git a/src/main/java/de/samply/exporter/focus/BeamService.java b/src/main/java/de/samply/exporter/focus/BeamService.java new file mode 100644 index 0000000..515b596 --- /dev/null +++ b/src/main/java/de/samply/exporter/focus/BeamService.java @@ -0,0 +1,126 @@ +package de.samply.exporter.focus; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.samply.app.ProjectManagerConst; +import de.samply.bridgehead.BridgeheadConfiguration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.UUID; + +@Service +public class BeamService { + + private final String projectManagerId; + private final String ttl; + private final FailureStrategy failureStrategy; + private final BridgeheadConfiguration bridgeheadConfiguration; + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public BeamService( + @Value(ProjectManagerConst.BEAM_PROJECT_MANAGER_ID_SV) String projectManagerId, + @Value(ProjectManagerConst.BEAM_TTL_SV) String ttl, + @Value(ProjectManagerConst.BEAM_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS_SV) int retriesBackoff, + @Value(ProjectManagerConst.BEAM_FAILURE_STRATEGY_MAX_TRIES_SV) int maxRetries, + BridgeheadConfiguration bridgeheadConfiguration) { + this.projectManagerId = projectManagerId; + this.ttl = ttl; + this.failureStrategy = createFailureStrategy(retriesBackoff, maxRetries); + this.bridgeheadConfiguration = bridgeheadConfiguration; + } + + public BeamRequest generateFocusBeamRequest(String exporterQuery, TaskType taskType, String bridgehead) throws BeamServiceException { + BeamRequest beamRequest = new BeamRequest(); + beamRequest.setId(generateId()); + beamRequest.setBody(exporterQuery); + beamRequest.setFrom(projectManagerId); + beamRequest.setTo(new String[]{fetchFocusBeamId(bridgehead)}); + beamRequest.setTtl(ttl); + beamRequest.setMetadata(createFocusQueryMetadata(taskType)); + beamRequest.setFailureStrategy(failureStrategy); + return beamRequest; + } + + public String generateId() { + return UUID.randomUUID().toString(); + } + + private String fetchFocusBeamId(String bridgehead) throws BeamServiceException { + String focusId = bridgeheadConfiguration.getFocusBeamId(bridgehead); + if (!StringUtils.hasText(focusId)) { + throw new BeamServiceException("Focus Beam ID for bridgehead " + bridgehead + " not found"); + } + return focusId; + } + + private String fetchFileDispatcherId(String bridgehead) throws BeamServiceException { + String focusId = bridgeheadConfiguration.getFileDispatcherBeamId(bridgehead); + if (!StringUtils.hasText(focusId)) { + throw new BeamServiceException("File Dispatcher Beam ID for bridgehead " + bridgehead + " not found"); + } + return focusId; + } + + private BeamRequestMetadata createFocusQueryMetadata(TaskType taskType) { + BeamRequestMetadata beamRequestMetadata = new BeamRequestMetadata(); + beamRequestMetadata.setProject(ProjectManagerConst.BEAM_FOCUS_METADATA_PROJECT); + beamRequestMetadata.setTaskType(taskType); + return beamRequestMetadata; + } + + private FailureStrategy createFailureStrategy(int backoff, int maxRetries) { + FailureStrategy failureStrategy = new FailureStrategy(); + RetryStrategy retryStrategy = new RetryStrategy(); + retryStrategy.setMaxTries(maxRetries); + retryStrategy.setBackoffInMilliseconds(backoff); + failureStrategy.setRetryStrategy(retryStrategy); + return failureStrategy; + } + + public BeamRequest[] deserializeFocusResponse(String focusResponse) throws BeamServiceException { + try { + return objectMapper.readValue(focusResponse, BeamRequest[].class); + } catch (JsonProcessingException e) { + throw new BeamServiceException(e); + } + } + + public String serializeFocusQuery(BeamRequest beamRequest) throws BeamServiceException { + try { + BeamRequest[] focusQueries = {beamRequest}; + return objectMapper.writeValueAsString(focusQueries); + } catch (JsonProcessingException e) { + throw new BeamServiceException(e); + } + } + + public BeamRequest generateExporterFileTransferBeamRequest(String bridgehead, String exporterExecutionId, String targetBeamId) { + BeamRequest beamRequest = new BeamRequest(); + beamRequest.setId(generateId()); + beamRequest.setBody(createExporterFileTransferBody(exporterExecutionId, targetBeamId)); + beamRequest.setFrom(projectManagerId); + beamRequest.setTo(new String[]{fetchFileDispatcherId(bridgehead)}); + beamRequest.setTtl(ttl); + beamRequest.setFailureStrategy(failureStrategy); + return beamRequest; + } + + private String createExporterFileTransferBody(String exporterExecutionId, String targetBeamId) { + try { + return createExporterFileTransferBodyWithoutExceptionHandling(exporterExecutionId, targetBeamId); + } catch (JsonProcessingException e) { + throw new BeamServiceException(e); + } + } + + private String createExporterFileTransferBodyWithoutExceptionHandling(String exporterExecutionId, String targetBeamId) throws JsonProcessingException { + FileTransferBody body = new FileTransferBody(); + body.setExporterUrl(ProjectManagerConst.EXPORTER_FETCH_QUERY_EXECUTION_URL_PATH + exporterExecutionId); + body.setBeamReceiver(targetBeamId); + return objectMapper.writeValueAsString(body); + } + +} diff --git a/src/main/java/de/samply/exporter/focus/BeamServiceException.java b/src/main/java/de/samply/exporter/focus/BeamServiceException.java new file mode 100644 index 0000000..09bc44c --- /dev/null +++ b/src/main/java/de/samply/exporter/focus/BeamServiceException.java @@ -0,0 +1,12 @@ +package de.samply.exporter.focus; + +public class BeamServiceException extends RuntimeException { + public BeamServiceException(String message) { + super(message); + } + + public BeamServiceException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/de/samply/exporter/focus/FileTransferBody.java b/src/main/java/de/samply/exporter/focus/FileTransferBody.java new file mode 100644 index 0000000..7756756 --- /dev/null +++ b/src/main/java/de/samply/exporter/focus/FileTransferBody.java @@ -0,0 +1,13 @@ +package de.samply.exporter.focus; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FileTransferBody { + + @JsonProperty("exporter-url") + private String exporterUrl; + @JsonProperty("beam-receiver") + private String beamReceiver; +} diff --git a/src/main/java/de/samply/exporter/focus/FocusService.java b/src/main/java/de/samply/exporter/focus/FocusService.java deleted file mode 100644 index 5d0921e..0000000 --- a/src/main/java/de/samply/exporter/focus/FocusService.java +++ /dev/null @@ -1,92 +0,0 @@ -package de.samply.exporter.focus; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.samply.app.ProjectManagerConst; -import de.samply.bridgehead.BridgeheadConfiguration; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.util.UUID; - -@Service -public class FocusService { - - private final String projectManagerId; - private final String ttl; - private final FailureStrategy failureStrategy; - private final BridgeheadConfiguration bridgeheadConfiguration; - private final ObjectMapper objectMapper = new ObjectMapper(); - - - public FocusService( - @Value(ProjectManagerConst.FOCUS_PROJECT_MANAGER_ID_SV) String projectManagerId, - @Value(ProjectManagerConst.FOCUS_TTL_SV) String ttl, - @Value(ProjectManagerConst.FOCUS_FAILURE_STRATEGY_BACKOFF_IN_MILLISECONDS_SV) int retriesBackoff, - @Value(ProjectManagerConst.FOCUS_FAILURE_STRATEGY_MAX_TRIES_SV) int maxRetries, - BridgeheadConfiguration bridgeheadConfiguration) { - this.projectManagerId = projectManagerId; - this.ttl = ttl; - this.failureStrategy = createFailureStrategy(retriesBackoff, maxRetries); - this.bridgeheadConfiguration = bridgeheadConfiguration; - } - - public FocusQuery generateFocusQuery(String exporterQuery, TaskType taskType, String bridgehead) throws FocusServiceException { - FocusQuery focusQuery = new FocusQuery(); - focusQuery.setId(generateId()); - focusQuery.setBody(exporterQuery); - focusQuery.setFrom(projectManagerId); - focusQuery.setTo(fetchExporterFocusIds(bridgehead)); - focusQuery.setTtl(ttl); - focusQuery.setMetadata(createFocusQueryMetadata(taskType)); - focusQuery.setFailureStrategy(failureStrategy); - return focusQuery; - } - - public String generateId() { - return UUID.randomUUID().toString(); - } - - private String[] fetchExporterFocusIds(String bridgehead) throws FocusServiceException { - String focusId = bridgeheadConfiguration.getFocusId(bridgehead); - if (!StringUtils.hasText(focusId)) { - throw new FocusServiceException("Focus ID for bridgehead " + bridgehead + " not found"); - } - return new String[]{focusId}; - } - - private FocusQueryMetadata createFocusQueryMetadata(TaskType taskType) { - FocusQueryMetadata focusQueryMetadata = new FocusQueryMetadata(); - focusQueryMetadata.setProject(ProjectManagerConst.FOCUS_METADATA_PROJECT); - focusQueryMetadata.setTaskType(taskType); - return focusQueryMetadata; - } - - private FailureStrategy createFailureStrategy(int backoff, int maxRetries) { - FailureStrategy failureStrategy = new FailureStrategy(); - RetryStrategy retryStrategy = new RetryStrategy(); - retryStrategy.setMaxTries(maxRetries); - retryStrategy.setBackoffInMilliseconds(backoff); - failureStrategy.setRetryStrategy(retryStrategy); - return failureStrategy; - } - - public FocusQuery[] deserializeFocusResponse(String focusResponse) throws FocusServiceException { - try { - return objectMapper.readValue(focusResponse, FocusQuery[].class); - } catch (JsonProcessingException e) { - throw new FocusServiceException(e); - } - } - - public String serializeFocusQuery(FocusQuery focusQuery) throws FocusServiceException { - try { - FocusQuery[] focusQueries = {focusQuery}; - return objectMapper.writeValueAsString(focusQueries); - } catch (JsonProcessingException e) { - throw new FocusServiceException(e); - } - } - -} diff --git a/src/main/java/de/samply/exporter/focus/FocusServiceException.java b/src/main/java/de/samply/exporter/focus/FocusServiceException.java deleted file mode 100644 index 8c04867..0000000 --- a/src/main/java/de/samply/exporter/focus/FocusServiceException.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.samply.exporter.focus; - -public class FocusServiceException extends RuntimeException { - public FocusServiceException(String message) { - super(message); - } - - public FocusServiceException(Throwable cause) { - super(cause); - } - -} diff --git a/src/main/java/de/samply/exporter/focus/TaskType.java b/src/main/java/de/samply/exporter/focus/TaskType.java index c71797f..5c45833 100644 --- a/src/main/java/de/samply/exporter/focus/TaskType.java +++ b/src/main/java/de/samply/exporter/focus/TaskType.java @@ -3,5 +3,6 @@ public enum TaskType { CREATE, EXECUTE, - STATUS + STATUS, + FILE_TRANSFER } diff --git a/src/main/java/de/samply/notification/OperationType.java b/src/main/java/de/samply/notification/OperationType.java index b726754..1fb924d 100644 --- a/src/main/java/de/samply/notification/OperationType.java +++ b/src/main/java/de/samply/notification/OperationType.java @@ -18,5 +18,6 @@ public enum OperationType { EDIT_PROJECT, SEND_EMAIL, CREATE_CODER_WORKSPACE, - DELETE_CODER_WORKSPACE + DELETE_CODER_WORKSPACE, + TRANSFER_FILE_TO_CODER } diff --git a/src/main/java/de/samply/project/ProjectType.java b/src/main/java/de/samply/project/ProjectType.java index d7e8362..6b9371c 100644 --- a/src/main/java/de/samply/project/ProjectType.java +++ b/src/main/java/de/samply/project/ProjectType.java @@ -2,7 +2,6 @@ public enum ProjectType { EXPORT, - DATASHIELD - //, - //RESEARCH_ENVIRONMENT + DATASHIELD, + RESEARCH_ENVIRONMENT } diff --git a/src/main/resources/db/migration/V001__initialize_schema_and_tables.sql b/src/main/resources/db/migration/V001__initialize_schema_and_tables.sql index 2f9b864..dfe0ddd 100644 --- a/src/main/resources/db/migration/V001__initialize_schema_and_tables.sql +++ b/src/main/resources/db/migration/V001__initialize_schema_and_tables.sql @@ -116,14 +116,14 @@ CREATE TABLE samply.project_bridgehead_datashield CREATE TABLE samply.project_coder ( - id SERIAL PRIMARY KEY, - app_id TEXT NOT NULL, - app_secret TEXT NOT NULL, - email TEXT NOT NULL, - project_id BIGINT NOT NULL, - workspace_id TEXT, - created_at TIMESTAMP NOT NULL, - deleted_at TIMESTAMP + id SERIAL PRIMARY KEY, + app_id TEXT NOT NULL, + app_secret TEXT NOT NULL, + project_bridgehead_user_id BIGINT NOT NULL, + export_transferred BOOLEAN NOT NULL, + workspace_id TEXT, + created_at TIMESTAMP NOT NULL, + deleted_at TIMESTAMP ); ALTER TABLE samply.project @@ -159,8 +159,8 @@ ALTER TABLE samply.project_bridgehead_datashield REFERENCES samply.project_bridgehead (id); ALTER TABLE samply.project_coder - ADD CONSTRAINT fk_project FOREIGN KEY (project_id) - REFERENCES samply.project (id); + ADD CONSTRAINT fk_project_bridgehead_user FOREIGN KEY (project_bridgehead_user_id) + REFERENCES samply.project_bridgehead_user (id); CREATE INDEX idx_project_bridgehead_project_id ON samply.project_bridgehead (project_id); CREATE INDEX idx_project_bridgehead_user_project_bridgehead_id ON samply.project_bridgehead_user (project_bridgehead_id); @@ -169,4 +169,4 @@ CREATE INDEX idx_project_query_id ON samply.project (query_id); CREATE INDEX idx_notification_project_id ON samply.notification (project_id); CREATE INDEX idx_notification_user_action_notification_id ON samply.notification_user_action (notification_id); CREATE INDEX idx_project_bridgehead_datashield_project_bridgehead_id ON samply.project_bridgehead_datashield (project_bridgehead_id); -CREATE INDEX idx_project_coder_project_id ON samply.project_coder (project_id); +CREATE INDEX idx_project_coder_project_bridgehead_user_id ON samply.project_coder (project_bridgehead_user_id); diff --git a/src/main/resources/email-templates.json b/src/main/resources/email-templates.json index f67ec8a..daf6416 100644 --- a/src/main/resources/email-templates.json +++ b/src/main/resources/email-templates.json @@ -81,6 +81,24 @@ "DEFAULT": "default-results-changes-requested.html" } }, + "ANALYSIS_ACCEPTED": { + "subject": "Analysis accepted", + "files": { + "DEFAULT": "default-analysis-accepted.html" + } + }, + "ANALYSIS_REJECTED": { + "subject": "Analysis rejected", + "files": { + "DEFAULT": "default-analysis-rejected.html" + } + }, + "REQUEST_CHANGES_IN_PROJECT_ANALYSIS": { + "subject": "Changes in project analysis requested", + "files": { + "DEFAULT": "default-analysis-changes-requested.html" + } + }, "NEW_TOKEN_FOR_AUTHENTICATION_SCRIPT": { "subject": "New Authentication Script for DataSHIELD", "files": { diff --git a/src/main/resources/frontend-project-configs.json b/src/main/resources/frontend-project-configs.json index 16dfab5..6bb3462 100644 --- a/src/main/resources/frontend-project-configs.json +++ b/src/main/resources/frontend-project-configs.json @@ -13,6 +13,13 @@ "outputFormat": "EXCEL", "templateId": "ccp" } + }, + "CCP-Research-Environment": { + "fieldValues": { + "type": "RESEARCH_ENVIRONMENT", + "outputFormat": "CSV", + "templateId": "ccp" + } } } } diff --git a/src/main/resources/templates/default-analysis-accepted.html b/src/main/resources/templates/default-analysis-accepted.html new file mode 100644 index 0000000..1083c19 --- /dev/null +++ b/src/main/resources/templates/default-analysis-accepted.html @@ -0,0 +1,10 @@ + + + + + Analysis accepted in bridgeheads + + +

The analysis was accepted in bridgeheads

+ + diff --git a/src/main/resources/templates/default-analysis-changes-requested.html b/src/main/resources/templates/default-analysis-changes-requested.html new file mode 100644 index 0000000..d7a989c --- /dev/null +++ b/src/main/resources/templates/default-analysis-changes-requested.html @@ -0,0 +1,10 @@ + + + + + Requested changes in project analysis + + +

There were some changes requested in project analysis

+ + diff --git a/src/main/resources/templates/default-analysis-rejected.html b/src/main/resources/templates/default-analysis-rejected.html new file mode 100644 index 0000000..2be5f10 --- /dev/null +++ b/src/main/resources/templates/default-analysis-rejected.html @@ -0,0 +1,10 @@ + + + + + Analysis rejected in project + + +

The analysis was rejected in project

+ +