diff --git a/app/back-end/src/constants.py b/app/back-end/src/constants.py index 20bf7f8..955b4cd 100644 --- a/app/back-end/src/constants.py +++ b/app/back-end/src/constants.py @@ -35,6 +35,9 @@ WORKSPACE_AGGREGATE_ROUTE = "/workspace/aggregate" WORKSPACE_IMPORT_ROUTE = "/workspace/import" WORKSPACE_EXPORT_ROUTE = "/workspace/export" +WORKSPACE_DOWNLOAD_ROUTE = "/workspace/download" +WORKSPACE_MERGE_ROUTE = "/workspace/merge" +WORKSPACE_APPLY_ROUTE = "/workspace/apply" # Events CONSOLE_FEEDBACK_EVENT = "console_feedback" diff --git a/app/back-end/src/routes/workspace_apply_route.py b/app/back-end/src/routes/workspace_apply_route.py new file mode 100644 index 0000000..b88025d --- /dev/null +++ b/app/back-end/src/routes/workspace_apply_route.py @@ -0,0 +1,276 @@ +""" +Workspace apply route module. +This module defines routes for applying algorithms to files and saving the results to the user's +workspace. +""" + +# pylint: disable=import-error + +import os +import time # TODO: Remove this import once the apply logic is implemented +from flask import Blueprint, request, jsonify + +from src.setup.extensions import logger +from src.utils.helpers import socketio_emit_to_user_session +from src.utils.exceptions import UnexpectedError +from src.constants import ( + WORKSPACE_APPLY_ROUTE, + WORKSPACE_DIR, + CONSOLE_FEEDBACK_EVENT, + WORKSPACE_UPDATE_FEEDBACK_EVENT, +) + +workspace_apply_route_bp = Blueprint("workspace_apply_route", __name__) + + +@workspace_apply_route_bp.route( + f"{WORKSPACE_APPLY_ROUTE}/spliceai/", methods=["GET"] +) +def get_workspace_apply_spliceai(relative_path): + """ + Route to apply the SpliceAI algorithm to a file and save the result to the workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override' and 'applyTo' are provided + if "override" not in request.args or "applyTo" not in request.args: + return ( + jsonify({"error": "'override', and 'applyTo' parameters are required"}), + 400, + ) + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and merged + # content should be appended + # - apply_to: string + # - The path to the file to which the SpliceAI algorithm should be applied + # Destination file can be the same file so ensure correct handling + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + apply_to = os.path.join(WORKSPACE_DIR, uuid, request.args.get("applyTo")) + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Applying SpliceAI algorithm to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement SpliceAI algorithm apply and save logic using defined parameters + # [destination_path, override, apply_to] + # + + # TODO: Remove this sleep statement once the apply logic is implemented + time.sleep(1) # Simulate a delay for the apply process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "succ", + "message": f"SpliceAI algorithm was successfully applied to '{relative_path}'.", + }, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error( + "FileNotFoundError: %s while applying SpliceAI algorithm %s", e, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while applying SpliceAI algorithm" + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error( + "PermissionError: %s while applying SpliceAI algorithm %s", e, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while applying SpliceAI algorithm" + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error( + "UnexpectedError: %s while applying SpliceAI algorithm %s", e.message, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while applying SpliceAI algorithm" + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "SpliceAI algorithm was successfully applied"}), 200 + + +@workspace_apply_route_bp.route( + f"{WORKSPACE_APPLY_ROUTE}/cadd/", methods=["GET"] +) +def get_workspace_apply_cadd(relative_path): + """ + Route to apply the CADD algorithm to a file and save the result to the workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override' and 'applyTo' are provided + if "override" not in request.args or "applyTo" not in request.args: + return ( + jsonify({"error": "'override', and 'applyTo' parameters are required"}), + 400, + ) + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and merged + # content should be appended + # - apply_to: string + # - The path to the file to which the CADD algorithm should be applied + # Destination file can be the same file so ensure correct handling + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + apply_to = os.path.join(WORKSPACE_DIR, uuid, request.args.get("applyTo")) + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Applying CADD algorithm to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement CADD algorithm apply and save logic using defined parameters + # [destination_path, override, apply_to] + # + + # TODO: Remove this sleep statement once the apply logic is implemented + time.sleep(1) # Simulate a delay for the apply process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "succ", + "message": f"CADD algorithm was successfully applied to '{relative_path}'.", + }, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while applying CADD algorithm %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while applying CADD algorithm " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while applying CADD algorithm %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while applying CADD algorithm {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error( + "UnexpectedError: %s while applying CADD algorithm %s", e.message, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while applying CADD algorithm " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "CADD algorithm was successfully applied"}), 200 diff --git a/app/back-end/src/routes/workspace_download_route.py b/app/back-end/src/routes/workspace_download_route.py new file mode 100644 index 0000000..ad1fe69 --- /dev/null +++ b/app/back-end/src/routes/workspace_download_route.py @@ -0,0 +1,369 @@ +""" +Workspace download route module. +This module defines routes for downloading data from external sources +and saving it to the user's workspace. +""" + +# pylint: disable=import-error + +import os +import time # TODO: Remove this import once the download logic is implemented +from flask import Blueprint, request, jsonify + +from src.setup.extensions import logger +from src.utils.helpers import socketio_emit_to_user_session +from src.utils.exceptions import UnexpectedError +from src.constants import ( + WORKSPACE_DOWNLOAD_ROUTE, + WORKSPACE_DIR, + CONSOLE_FEEDBACK_EVENT, + WORKSPACE_UPDATE_FEEDBACK_EVENT, +) + +workspace_download_route_bp = Blueprint("workspace_download_route", __name__) + + +@workspace_download_route_bp.route( + f"{WORKSPACE_DOWNLOAD_ROUTE}/lovd/", methods=["GET"] +) +def get_workspace_download_lovd(relative_path): + """ + Download LOVD data for a specific gene and save it to the user's workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override' and 'gene' are provided + if "override" not in request.args or "gene" not in request.args: + return jsonify({"error": "'override' and 'gene' parameters are required"}), 400 + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and download + # content should be appended + # - gene: string + # - Determines the gene to be downloaded from LOVD (e.g. 'eys') + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + gene = request.args.get("gene") + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Downloading LOVD data for gene '{gene}' to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement LOVD data download and save logic using defined parameters + # [destination_path, override, gene] + # + + # TODO: Remove this sleep statement once the download logic is implemented + time.sleep(1) # Simulate a delay for the download process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + {"type": "succ", "message": f"LOVD file '{relative_path}' was saved successfully."}, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while downloading LOVD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while downloading LOVD {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while downloading LOVD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while downloading LOVD {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error("UnexpectedError: %s while downloading LOVD %s", e.message, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while downloading LOVD " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "LOVD data downloaded successfully"}), 200 + + +@workspace_download_route_bp.route( + f"{WORKSPACE_DOWNLOAD_ROUTE}/clinvar/", methods=["GET"] +) +def get_workspace_download_clinvar(relative_path): + """ + Download ClinVar data for a specific gene and save it to the user's workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override' and 'gene' are provided + if "override" not in request.args or "gene" not in request.args: + return jsonify({"error": "'override' and 'gene' parameters are required"}), 400 + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and download + # content should be appended + # - gene: string + # - Determines the gene to be downloaded from LOVD (e.g. 'eys') + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + gene = request.args.get( + "gene", + ) + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Downloading ClinVar data for gene '{gene}' to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement ClinVar data download and save logic using defined parameters + # [destination_path, override, gene] + # + + # TODO: Remove this sleep statement once the download logic is implemented + time.sleep(1) # Simulate a delay for the download process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + {"type": "succ", "message": f"ClinVar file '{relative_path}' was saved successfully."}, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while downloading ClinVar %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while downloading ClinVar {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while downloading ClinVar %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while downloading ClinVar {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error( + "UnexpectedError: %s while downloading ClinVar %s", e.message, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while downloading ClinVar " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "CliVar data downloaded successfullly"}), 200 + + +@workspace_download_route_bp.route( + f"{WORKSPACE_DOWNLOAD_ROUTE}/gnomad/", methods=["GET"] +) +def get_workspace_download_gnomad(relative_path): + """ + Download gnomAD data for a specific gene and save it to the user's workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override' and 'gene' are provided + if "override" not in request.args or "gene" not in request.args: + return jsonify({"error": "'override' and 'gene' parameters are required"}), 400 + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and download + # content should be appended + # - gene: string + # - Determines the gene to be downloaded from LOVD (e.g. 'eys') + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + gene = request.args.get("gene") + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Downloading gnomAD data for gene '{gene}' to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement gnomAD data download and save logic using defined parameters + # [destination_path, override, gene] + # + + # TODO: Remove this sleep statement once the download logic is implemented + time.sleep(1) # Simulate a delay for the download process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + {"type": "succ", "message": f"gnomAD file '{relative_path}' was saved successfully."}, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while downloading gnomAD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while downloading gnomAD {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while downloading gnomAD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while downloading gnomAD {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error("UnexpectedError: %s while downloading gnomAD %s", e.message, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while downloading gnomAD " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "gnomAD data downloaded successfullly"}), 200 diff --git a/app/back-end/src/routes/workspace_merge_route.py b/app/back-end/src/routes/workspace_merge_route.py new file mode 100644 index 0000000..6de3f6c --- /dev/null +++ b/app/back-end/src/routes/workspace_merge_route.py @@ -0,0 +1,284 @@ +""" +Workspace merge route module. +This module defines routes for merging data from different sources and saving the merged data to +the user's workspace. +""" + +# pylint: disable=import-error + +import os +import time # TODO: Remove this import once the merge logic is implemented +from flask import Blueprint, request, jsonify + +from src.setup.extensions import logger +from src.utils.helpers import socketio_emit_to_user_session +from src.utils.exceptions import UnexpectedError +from src.constants import ( + WORKSPACE_MERGE_ROUTE, + WORKSPACE_DIR, + CONSOLE_FEEDBACK_EVENT, + WORKSPACE_UPDATE_FEEDBACK_EVENT, +) + +workspace_merge_route_bp = Blueprint("workspace_merge_route", __name__) + + +@workspace_merge_route_bp.route( + f"{WORKSPACE_MERGE_ROUTE}/lovd_gnomad/", methods=["GET"] +) +def get_workspace_merge_lovd_gnomad(relative_path): + """ + Route to merge LOVD and gnomAD data and save the merged data to the workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override', 'lovdFile' and 'gnomadFile' are provided + if ( + "override" not in request.args + or "lovdFile" not in request.args + or "gnomadFile" not in request.args + ): + return ( + jsonify({"error": "'override', 'lovdFile' and 'gnomadFile' parameters are required"}), + 400, + ) + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and merged + # content should be appended + # - lovd_file: string + # - The path to the LOVD file to be used in merge + # - gnomad_file: string + # - The path to the gnomAD file to be used in merge + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + lovd_file = os.path.join(WORKSPACE_DIR, uuid, request.args.get("lovdFile")) + gnomad_file = os.path.join(WORKSPACE_DIR, uuid, request.args.get("gnomadFile")) + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Merging LOVD and gnomAD data to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement LOVD and gnomAD data merge and save logic using defined parameters + # [destination_path, override, lovd_file, gnomad_file] + # + + # TODO: Remove this sleep statement once the merge logic is implemented + time.sleep(1) # Simulate a delay for the merge process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "succ", + "message": f"LOVD and gnomAD merge to '{relative_path}' was successful.", + }, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while merging LOVD and gnomAD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while merging LOVD and gnomAD " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while merging LOVD and gnomAD %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while merging LOVD and gnomAD {destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error( + "UnexpectedError: %s while merging LOVD and gnomAD %s", e.message, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while merging LOVD and gnomAD " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "LOVD and gnomAD data merge successful"}), 200 + + +@workspace_merge_route_bp.route( + f"{WORKSPACE_MERGE_ROUTE}/lovd_clinvar/", methods=["GET"] +) +def get_workspace_merge_lovd_clinvar(relative_path): + """ + Route to merge LOVD and ClinVar data and save the merged data to the workspace. + """ + + # Check if 'uuid' and 'sid' are provided in the headers + if "uuid" not in request.headers or "sid" not in request.headers: + return jsonify({"error": "UUID and SID headers are required"}), 400 + + uuid = request.headers.get("uuid") + sid = request.headers.get("sid") + + # Check if 'override', 'lovdFile' and 'gnomadFile' are provided + if ( + "override" not in request.args + or "lovdFile" not in request.args + or "clinvarFile" not in request.args + ): + return ( + jsonify({"error": "'override', 'lovdFile' and 'clinvarFile' parameters are required"}), + 400, + ) + + # Explanation about the parameters: + # - destination_path: string + # - The path to the destination file (where to save it) in the user's workspace + # Destination file can either be a new file or an existing file, check its existence + # - override: boolean + # - If true, the existing destination file should be overridden + # - If false, the existing destination file should not be overridden and merged + # content should be appended + # - lovd_file: string + # - The path to the LOVD file to be used in merge + # - clinvar_file: string + # - The path to the ClinVar file to be used in merge + + destination_path = os.path.join(WORKSPACE_DIR, uuid, relative_path) + override = request.args.get("override") + lovd_file = os.path.join(WORKSPACE_DIR, uuid, request.args.get("lovdFile")) + clinvar_file = os.path.join(WORKSPACE_DIR, uuid, request.args.get("clinvarFile")) + + try: + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "info", + "message": f"Merging LOVD and ClinVar data to '{relative_path}' with " + + f"override: '{override}'...", + }, + uuid, + sid, + ) + + # + # TODO: Implement LOVD and ClinVar data merge and save logic using defined parameters + # [destination_path, override, lovd_file, clinvar_file] + # + + # TODO: Remove this sleep statement once the merge logic is implemented + time.sleep(1) # Simulate a delay for the merge process + + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "succ", + "message": f"LOVD and ClinVar merge to '{relative_path}' was successful.", + }, + uuid, + sid, + ) + + socketio_emit_to_user_session( + WORKSPACE_UPDATE_FEEDBACK_EVENT, + {"status": "updated"}, + uuid, + sid, + ) + + except FileNotFoundError as e: + logger.error("FileNotFoundError: %s while merging LOVD and ClinVar %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"FileNotFoundError: {e} while merging LOVD and ClinVar " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Requested file not found"}), 404 + except PermissionError as e: + logger.error("PermissionError: %s while merging LOVD and ClinVar %s", e, destination_path) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"PermissionError: {e} while merging LOVD and ClinVar " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "Permission denied"}), 403 + except UnexpectedError as e: + logger.error( + "UnexpectedError: %s while merging LOVD and ClinVar %s", e.message, destination_path + ) + # Emit a feedback to the user's console + socketio_emit_to_user_session( + CONSOLE_FEEDBACK_EVENT, + { + "type": "errr", + "message": f"UnexpectedError: {e.message} while merging LOVD and ClinVar " + + f"{destination_path}", + }, + uuid, + sid, + ) + return jsonify({"error": "An internal error occurred"}), 500 + + return jsonify({"message": "LOVD and ClinVar data merge successful"}), 200 diff --git a/app/back-end/src/setup/router.py b/app/back-end/src/setup/router.py index 102e080..3088ba6 100644 --- a/app/back-end/src/setup/router.py +++ b/app/back-end/src/setup/router.py @@ -19,6 +19,9 @@ from src.routes.workspace_aggregate_route import workspace_aggregate_route_bp from src.routes.workspace_export_route import workspace_export_route_bp from src.routes.workspace_import_route import workspace_import_route_bp +from src.routes.workspace_download_route import workspace_download_route_bp +from src.routes.workspace_merge_route import workspace_merge_route_bp +from src.routes.workspace_apply_route import workspace_apply_route_bp def router(prefix): @@ -37,6 +40,9 @@ def router(prefix): router_bp = Blueprint("router", __name__, url_prefix=prefix) # Register API routes with the main router blueprint + router_bp.register_blueprint(workspace_apply_route_bp) + router_bp.register_blueprint(workspace_merge_route_bp) + router_bp.register_blueprint(workspace_download_route_bp) router_bp.register_blueprint(workspace_export_route_bp) router_bp.register_blueprint(workspace_import_route_bp) router_bp.register_blueprint(workspace_aggregate_route_bp) diff --git a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/applyGroupButtons.tsx b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/applyGroupButtons.tsx index c099c10..4f6229d 100644 --- a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/applyGroupButtons.tsx +++ b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/applyGroupButtons.tsx @@ -1,6 +1,9 @@ import { ToolbarGroupItem, ToolbarGroupItemProps } from '@/features/editor/components/toolbarView'; -import { useToolbarContext } from '@/features/editor/hooks'; +import { useToolbarContext, useWorkspaceContext } from '@/features/editor/hooks'; +import { findUniqueFileName, generateTimestamp } from '@/features/editor/utils'; import { useStatusContext } from '@/hooks'; +import { axios } from '@/lib'; +import { Endpoints } from '@/types'; import { Deblur as DeblurIcon } from '@mui/icons-material'; import { useCallback, useMemo } from 'react'; @@ -8,6 +11,7 @@ export interface ApplyGroupButtonsProps {} export const ApplyGroupButtons: React.FC = () => { const { blockedStateUpdate } = useStatusContext(); + const { fileTree } = useWorkspaceContext(); const { saveTo, override, applyTo, applyErrorStateUpdate } = useToolbarContext(); const applySpliceAiClick = useCallback(async () => { @@ -19,16 +23,15 @@ export const ApplyGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Apply SpliceAI Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n applyTo:', - applyTo - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `spliceai_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_APPLY}/spliceai/${savePath}`, { + params: { + override, + applyTo, + }, + }); } catch (error) { console.error('Error applying SpliceAI:', error); } finally { @@ -45,16 +48,15 @@ export const ApplyGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Merge LOVD & ClinVar Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n applyTo:', - applyTo - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `cadd_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_APPLY}/cadd/${savePath}`, { + params: { + override, + applyTo, + }, + }); } catch (error) { console.error('Error applying CADD:', error); } finally { diff --git a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/downloadGroupButtons.tsx b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/downloadGroupButtons.tsx index 39a4133..0a420f9 100644 --- a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/downloadGroupButtons.tsx +++ b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/downloadGroupButtons.tsx @@ -1,6 +1,9 @@ import { ToolbarGroupItem, ToolbarGroupItemProps } from '@/features/editor/components/toolbarView'; -import { useToolbarContext } from '@/features/editor/hooks'; +import { useToolbarContext, useWorkspaceContext } from '@/features/editor/hooks'; +import { findUniqueFileName, generateTimestamp } from '@/features/editor/utils'; import { useStatusContext } from '@/hooks'; +import { axios } from '@/lib'; +import { Endpoints } from '@/types'; import { Download as DownloadIcon } from '@mui/icons-material'; import { useCallback, useMemo } from 'react'; @@ -8,22 +11,22 @@ export interface DownloadGroupButtonsProps {} export const DownloadGroupButtons: React.FC = () => { const { blockedStateUpdate } = useStatusContext(); + const { fileTree } = useWorkspaceContext(); const { saveTo, override, gene } = useToolbarContext(); const handleDownloadLovdClick = useCallback(async () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Download Lovd Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n gene:', - gene - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `lovd_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_DOWNLOAD}/lovd/${savePath}`, { + params: { + override, + gene, + }, + }); } catch (error) { console.error('Error downloading LOVD file:', error); } finally { @@ -35,16 +38,15 @@ export const DownloadGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Download Clinvar Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n gene:', - gene - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `clinvar_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_DOWNLOAD}/clinvar/${savePath}`, { + params: { + override, + gene, + }, + }); } catch (error) { console.error('Error downloading ClinVar file:', error); } finally { @@ -56,16 +58,15 @@ export const DownloadGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Download Gnomad Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n gene:', - gene - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `gnomad_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_DOWNLOAD}/gnomad/${savePath}`, { + params: { + override, + gene, + }, + }); } catch (error) { console.error('Error downloading gnomAD file:', error); } finally { diff --git a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/mergeGroupButtons.tsx b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/mergeGroupButtons.tsx index f7f585f..784314e 100644 --- a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/mergeGroupButtons.tsx +++ b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupButtons/mergeGroupButtons.tsx @@ -1,6 +1,9 @@ import { ToolbarGroupItem, ToolbarGroupItemProps } from '@/features/editor/components/toolbarView'; -import { useToolbarContext } from '@/features/editor/hooks'; +import { useToolbarContext, useWorkspaceContext } from '@/features/editor/hooks'; +import { findUniqueFileName, generateTimestamp } from '@/features/editor/utils'; import { useStatusContext } from '@/hooks'; +import { axios } from '@/lib'; +import { Endpoints } from '@/types'; import { MergeType as MergeTypeIcon } from '@mui/icons-material'; import { useCallback, useMemo } from 'react'; @@ -8,6 +11,7 @@ export interface MergeGroupButtonsProps {} export const MergeGroupButtons: React.FC = () => { const { blockedStateUpdate } = useStatusContext(); + const { fileTree } = useWorkspaceContext(); const { saveTo, override, @@ -29,18 +33,16 @@ export const MergeGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Merge LOVD & gnomAD Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n lovd:', - lovdFile, - '\n gnomad:', - gnomadFile - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `lovd_gnomad_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_MERGE}/lovd_gnomad/${savePath}`, { + params: { + override, + lovdFile, + gnomadFile, + }, + }); } catch (error) { console.error('Error merging LOVD & gnomAD files:', error); } finally { @@ -58,18 +60,16 @@ export const MergeGroupButtons: React.FC = () => { blockedStateUpdate(true); try { - console.log( - 'Clicked Merge LOVD & ClinVar Button! Params:\n saveTo:', - saveTo, - '\n override:', - override, - '\n lovd:', - lovdFile, - '\n clinvar:', - clinvarFile - ); + const timestamp = generateTimestamp(); + const savePath = saveTo !== '/' ? saveTo : findUniqueFileName(fileTree, `lovd_clinvar_${timestamp}.csv`); - await new Promise((resolve) => setTimeout(resolve, 1000)); // TODO: remove this line + await axios.get(`${Endpoints.WORKSPACE_MERGE}/lovd_clinvar/${savePath}`, { + params: { + override, + lovdFile, + clinvarFile, + }, + }); } catch (error) { console.error('Error merging LOVD & ClinVar files:', error); } finally { diff --git a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupParams/applyGroupParams.tsx b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupParams/applyGroupParams.tsx index 9617c80..89c43ca 100644 --- a/app/front-end/src/features/editor/components/toolbarView/toolbarGroupParams/applyGroupParams.tsx +++ b/app/front-end/src/features/editor/components/toolbarView/toolbarGroupParams/applyGroupParams.tsx @@ -111,7 +111,7 @@ export const ApplyGroupParams: React.FC = () => { - + = () => { - + { + const now = new Date(); + return ( + now.getFullYear() + + '-' + + String(now.getMonth() + 1).padStart(2, '0') + + '-' + + String(now.getDate()).padStart(2, '0') + + '_' + + String(now.getHours()).padStart(2, '0') + + '-' + + String(now.getMinutes()).padStart(2, '0') + + '-' + + String(now.getSeconds()).padStart(2, '0') + ); +}; diff --git a/app/front-end/src/features/editor/utils/index.ts b/app/front-end/src/features/editor/utils/index.ts index acd3fe7..e8b1ca1 100644 --- a/app/front-end/src/features/editor/utils/index.ts +++ b/app/front-end/src/features/editor/utils/index.ts @@ -1 +1 @@ -export { doesFileExist, findUniqueFileName, getFileExtension, getIconFromFileType, isExpandable, getWorkspaceArray } from './helpers'; +export { doesFileExist, findUniqueFileName, getFileExtension, getIconFromFileType, isExpandable, getWorkspaceArray, generateTimestamp } from './helpers'; diff --git a/app/front-end/src/types/constants/endpoints.ts b/app/front-end/src/types/constants/endpoints.ts index 689abad..8c3ce82 100644 --- a/app/front-end/src/types/constants/endpoints.ts +++ b/app/front-end/src/types/constants/endpoints.ts @@ -20,6 +20,12 @@ * @property {string} WORKSPACE_CREATE - The endpoint path for creating new items in the workspace. * @property {string} WORKSPACE_RENAME - The endpoint path for renaming items in the workspace. * @property {string} WORKSPACE_DELETE - The endpoint path for deleting items in the workspace. + * @property {string} WORKSPACE_AGGREGATE - The endpoint path for aggregating workspace items. + * @property {string} WORKSPACE_IMPORT - The endpoint path for importing workspace items. + * @property {string} WORKSPACE_EXPORT - The endpoint path for exporting workspace items. + * @property {string} WORKSPACE_DOWNLOAD - The endpoint path for downloading workspace items. + * @property {string} WORKSPACE_MERGE - The endpoint path for merging workspace items. + * @property {string} WORKSPACE_APPLY - The endpoint path for applying workspace items. * * @example * // Example usage of the Endpoints object @@ -40,4 +46,7 @@ export const Endpoints = { WORKSPACE_AGGREGATE: `/workspace/aggregate`, WORKSPACE_IMPORT: `/workspace/import`, WORKSPACE_EXPORT: `/workspace/export`, + WORKSPACE_DOWNLOAD: `/workspace/download`, + WORKSPACE_MERGE: `/workspace/merge`, + WORKSPACE_APPLY: `/workspace/apply`, };