diff --git a/apps/cli/utilities/aat_wrest/aat_wrest/metadata_wrester.py b/apps/cli/utilities/aat_wrest/aat_wrest/metadata_wrester.py index 05e0d12a48262b9dd8496a300b25bb3a0f601649..5eeda69e8f0b684b14a47ed09edd573e25cdb1a7 100644 --- a/apps/cli/utilities/aat_wrest/aat_wrest/metadata_wrester.py +++ b/apps/cli/utilities/aat_wrest/aat_wrest/metadata_wrester.py @@ -348,3 +348,39 @@ class WrestWorkflowMetadata: self.conn.close() return make_json + + def wrest_curator(self) -> json: + """ + Given a locator, returns the product information necessary to run curator on it. + + :return: JSON containing product information. + """ + query = """ + SELECT sp.science_product_type, + eb.telescope, + eb.project_code + FROM science_products sp + JOIN execution_blocks eb ON sp.science_product_locator = eb.science_product_locator + WHERE sp.science_product_locator = %(spl)s; + """ + make_json = {} + try: + cursor = self.conn.cursor() + cursor.execute(query, {"spl": self.spl[0]}) + data = cursor.fetchall() + if data: + make_json = json.dumps( + { + "product_type": str(data[0][0]).lower().replace(' ', '_'), + "telescope": data[0][1], + "projectCode": data[0][2] + } + ) + else: + self.logger.error( + f"ERROR: aat-wrest query returned no results!" + f" The database appears to be missing information for spl id {self.spl[0]}!" + ) + finally: + self.conn.close() + return make_json diff --git a/apps/cli/utilities/aat_wrest/aat_wrest/wrest.py b/apps/cli/utilities/aat_wrest/aat_wrest/wrest.py index 375c7c67e16e0619587083027b6ade3f4aee8f22..8de40dcc8c0eb4ad7ee3ffa55952f7c571b98c78 100644 --- a/apps/cli/utilities/aat_wrest/aat_wrest/wrest.py +++ b/apps/cli/utilities/aat_wrest/aat_wrest/wrest.py @@ -107,6 +107,13 @@ def parser() -> argparse.ArgumentParser: required=False, help="Find the basic product information for the provided product locator(s)", ) + arg_parser.add_argument( + "--curator", + nargs=1, + action="store", + required=False, + help="Find the product information necessary to run curator on the provided product locator", + ) return arg_parser @@ -140,6 +147,8 @@ def determine_wrester(connection: MDDBConnector, args: argparse.Namespace): data = json.dumps({"reimaging_locator": reimaging_locator, "parameter_locator": parameter_locator}) elif args.product: data = WrestWorkflowMetadata(connection, spl=args.product).wrest_product_info() + elif args.curator: + data = WrestWorkflowMetadata(connection, spl=args.curator).wrest_curator() else: data = None diff --git a/apps/web/src/app/workspaces/ws-home/ws-home.component.html b/apps/web/src/app/workspaces/ws-home/ws-home.component.html index 31f2c9ae4ea54a6e3667f18c941a25f483247bab..5b2e845e8afac3784cbe04355d9249bae708d2a9 100644 --- a/apps/web/src/app/workspaces/ws-home/ws-home.component.html +++ b/apps/web/src/app/workspaces/ws-home/ws-home.component.html @@ -143,3 +143,50 @@ </div> </div> </div> +<div class="container border rounded py-3 my-3"> + <h4>Curator</h4> + <div class="row p-3 mt-4"> + <div class="col-6"> + <div class="md-form"> + <label for="curSplInput" class="">Science Product</label> + <input type="text" id="curSplInput" class="form-control" + (change)="setProductLocator($event.target.value)"/> + </div> + </div> + <div class="col-6"> + <div class="md-form"> + <label for="curDataInput" class="">Data Location (Optional)</label> + <input type="text" id="curDataInput" class="form-control" + (change)="setDataLocation($event.target.value)"/> + </div> + </div> + <div class="col-3"> + <div class="md-form"> + <label for="curTargetInput" class="">Target List (Optional)</label> + <input type="text" id="curTargetInput" class="form-control" + (change)="setTargetList($event.target.value)"/> + </div> + </div> + <div class="col-3"> + <div class="md-form"> + <label for="curUserEmail" class="">Email Address</label> + <input type="text" id="curUserEmail" class="form-control" + [value]="userEmail" (change)="setUserEmail($event.target.value)"/> + </div> + </div> + </div> + <div id="full-curator-button-container" class="d-flex justify-content-left py-2"> + <div class="d-flex px-2"> + <button type="button" class="btn btn-lg btn-primary" id="full-curator-submit" + (click)="LaunchCuratorCapabilityOnClick('full')"> + Launch full curation + </button> + </div> + <div id="partial-curator-button-container" class="d-flex px-2"> + <button type="button" class="btn btn-lg btn-info" id="partial-curator-submit" + (click)="LaunchCuratorCapabilityOnClick('partial')"> + Launch partial curation + </button> + </div> + </div> +</div> diff --git a/apps/web/src/app/workspaces/ws-home/ws-home.component.ts b/apps/web/src/app/workspaces/ws-home/ws-home.component.ts index 539dcd3248203672623476fe7e26a1bff8842029..e12f7d0ab6bd911fe37d1ad3b0bc474779b95c40 100644 --- a/apps/web/src/app/workspaces/ws-home/ws-home.component.ts +++ b/apps/web/src/app/workspaces/ws-home/ws-home.component.ts @@ -34,6 +34,8 @@ export class WsHomeComponent implements OnInit { public inputFileList: FileList; public cmsPath: string; public sdmId: string; + public dataLocation: string; + public targetList: string; constructor( private capabilityLauncher: CapabilityLauncherService, @@ -117,6 +119,24 @@ export class WsHomeComponent implements OnInit { }); } + /** + * OnClick method that creates a capability request for a curator capability and submits it with the parameters: + * - Curator type (full or partial) + * - EB Product Locator + * - Data location path + * - Target list + * - User email + */ + LaunchCuratorCapabilityOnClick(curatorType: string): void { + this.launchCapability('curator', { + curator_type: curatorType, + product_locator: this.productLocator, + data_location: this.dataLocation, + target_list: this.targetList, + user_email: this.userEmail, + }); + } + /** * method that sets the user input Science Product Locator for the download capability * @param spl the Science Product Locator to download @@ -165,6 +185,22 @@ export class WsHomeComponent implements OnInit { this.sdmId = id; } + /** + * Sets the data location for curator + * @param path Data location path for curator + */ + setDataLocation(path: string): void { + this.dataLocation = path; + } + + /** + * Sets the target list for curator + * @param targetList Target list for curator + */ + setTargetList(targetList: string): void { + this.targetList = targetList; + } + /** * Method that uses the capabilityLauncher service to launch a capability * @param capabilityName Name of capability diff --git a/shared/workspaces/alembic/versions/3eae1178cace_add_curator_workflow.py b/shared/workspaces/alembic/versions/3eae1178cace_add_curator_workflow.py new file mode 100644 index 0000000000000000000000000000000000000000..05f522841b462b6b20f1ef6c9815c518d5bb2d4c --- /dev/null +++ b/shared/workspaces/alembic/versions/3eae1178cace_add_curator_workflow.py @@ -0,0 +1,100 @@ +"""add curator workflow + +Revision ID: 3eae1178cace +Revises: 61cbcd1d83f7 +Create Date: 2023-10-12 11:25:34.165586 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3eae1178cace' +down_revision = '61cbcd1d83f7' +branch_labels = None +depends_on = None + +wf_name = "curator" + +curator_condor = """executable = curator.sh +arguments = metadata.json {{curator_type}} + +output = curator.out +error = curator.err +log = condor.log + +should_transfer_files = NO ++WantIOProxy = True +request_memory = {{ramInGb}} +getenv = True +environment = "CAPO_PATH=/home/casa/capo" +requirements = HasLustre == True + +queue + +""" + +curator_sh = """#!/bin/sh +set -o errexit + +SBIN_PATH=/lustre/aoc/cluster/pipeline/$CAPO_PROFILE/workspaces/sbin + +${SBIN_PATH}/ingest_envoy curate --$2 $1 + +""" + +metadata_json = """{ + "product_locator": "{{product_locator}}", + "data_location": "{{data_location}}", + "product_type": "{{product_type}}", + "target_list": ["{{target_list}}"], + "projectMetadata": { + "telescope": "{{telescope}}", + "projectCode": "{{project_code}}" + } +} +""" + + +def upgrade(): + op.execute( + f""" + INSERT INTO workflows (workflow_name, requires_lustre) VALUES (E'{wf_name}', true) + """ + ) + + op.execute( + f""" + INSERT INTO workflow_templates (filename, content, workflow_name) + VALUES ('curator.condor', E'{curator_condor}', E'{wf_name}') + """ + ) + + op.execute( + f""" + INSERT INTO workflow_templates (filename, content, workflow_name) + VALUES ('curator.sh', E'{curator_sh}', E'{wf_name}') + """ + ) + + op.execute( + f""" + INSERT INTO workflow_templates (filename, content, workflow_name) + VALUES ('metadata.json', E'{metadata_json}', E'{wf_name}') + """ + ) + + +def downgrade(): + op.execute( + f""" + DELETE FROM workflow_templates WHERE workflow_name = E'{wf_name}' + """ + ) + + op.execute( + f""" + DELETE FROM workflows WHERE workflow_name = E'{wf_name}' + """ + ) diff --git a/shared/workspaces/alembic/versions/5939146da7bb_add_curator_capability.py b/shared/workspaces/alembic/versions/5939146da7bb_add_curator_capability.py new file mode 100644 index 0000000000000000000000000000000000000000..7650f177a93aa8412ef6946a1b9e79745ce8b81c --- /dev/null +++ b/shared/workspaces/alembic/versions/5939146da7bb_add_curator_capability.py @@ -0,0 +1,48 @@ +"""add curator capability + +Revision ID: 5939146da7bb +Revises: 3eae1178cace +Create Date: 2023-10-23 22:04:43.801226 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5939146da7bb' +down_revision = '3eae1178cace' +branch_labels = None +depends_on = None + +wf_name = "curator" + + +def upgrade(): + op.execute( + f""" + INSERT INTO capabilities (capability_name, max_jobs, single_version_only) + VALUES (E'{wf_name}', 10, true) + """ + ) + + op.execute( + f""" + INSERT INTO capability_state_machines (capability_name, machine_type, associated_workflows) + VALUES (E'{wf_name}', 'simple', '{{"workflow_name": "{wf_name}"}}') + """ + ) + + +def downgrade(): + op.execute( + f""" + DELETE FROM capability_state_machines WHERE capability_name = {wf_name} + """ + ) + + op.execute( + f""" + DELETE FROM capabilities WHERE capability_name = {wf_name} + """ + ) diff --git a/shared/workspaces/workspaces/workflow/services/workflow_service.py b/shared/workspaces/workspaces/workflow/services/workflow_service.py index c1f52d4f4489bfa393102da67a40c3697c00eb6a..77dfed54b3c7e250314e37914a1364a89351c071 100644 --- a/shared/workspaces/workspaces/workflow/services/workflow_service.py +++ b/shared/workspaces/workspaces/workflow/services/workflow_service.py @@ -500,6 +500,8 @@ class WorkflowService(WorkflowServiceIF): else parent_req.argument["sdmId"] ) argument = eb + elif in_name("curator"): + wrest_type = "--curator" else: logger.info(f"No wrester found for workflow {name}. Does it actually require metadata?") return wf_request