diff --git a/backend/dataall/modules/omics/api/mutations.py b/backend/dataall/modules/omics/api/mutations.py index 197dbeda7..87f7f0978 100644 --- a/backend/dataall/modules/omics/api/mutations.py +++ b/backend/dataall/modules/omics/api/mutations.py @@ -30,5 +30,3 @@ # ], # resolver=delete_omics_run, # ) - - diff --git a/backend/dataall/modules/omics/api/queries.py b/backend/dataall/modules/omics/api/queries.py index 978cb1369..57f99df0d 100644 --- a/backend/dataall/modules/omics/api/queries.py +++ b/backend/dataall/modules/omics/api/queries.py @@ -12,7 +12,7 @@ getOmicsWorkflow = gql.QueryField( name="getOmicsWorkflow", - args=[gql.Argument(name="workflowId", type=gql.NonNullableType(gql.String))], + args=[gql.Argument(name="workflowUri", type=gql.NonNullableType(gql.String))], type=gql.Ref("OmicsWorkflow"), resolver=get_omics_workflow, ) diff --git a/backend/dataall/modules/omics/api/resolvers.py b/backend/dataall/modules/omics/api/resolvers.py index aba04b69d..4f0cdc43a 100644 --- a/backend/dataall/modules/omics/api/resolvers.py +++ b/backend/dataall/modules/omics/api/resolvers.py @@ -1,12 +1,13 @@ import logging from dataall.base.api.context import Context -from dataall.core.stacks.api import stack_helper from dataall.base.db import exceptions from dataall.modules.omics.services.omics_service import OmicsService +from dataall.modules.omics.services.omics_enums import OmicsWorkflowType from dataall.modules.omics.db.models import OmicsRun, OmicsWorkflow log = logging.getLogger(__name__) + class RequestValidator: """Aggregates all validation logic for operating with omics""" @staticmethod @@ -30,6 +31,7 @@ def _required(data: dict, name: str): if not data.get(name): raise exceptions.RequiredParameter(name) + def create_omics_run(context: Context, source, input=None): RequestValidator.validate_creation_request(input) # request = OmicsRunCreationRequest.from_dict(input) @@ -52,13 +54,10 @@ def list_omics_workflows(context: Context, source, filter: dict = None): return OmicsService.list_omics_workflows(filter) -def get_omics_workflow(context: Context, source, workflowId: str = None): - RequestValidator.required_uri(workflowId) - return OmicsService.get_omics_workflow(workflowId) +def get_omics_workflow(context: Context, source, workflowUri: str = None): + RequestValidator.required_uri(workflowUri) + return OmicsService.get_omics_workflow(workflowUri) -def run_omics_workflow(context: Context, source, workflowId: str = None, workflowType: str = 'READY2RUN', roleArn: str = None, parameters: str = None): - RequestValidator.required_uri(workflowId) - return OmicsService.run_omics_workflow(workflowId,workflowType,roleArn,parameters) def delete_omics_run(context: Context, source, runUri: str = None, deleteFromAWS: bool = None): RequestValidator.required_uri(runUri) @@ -67,11 +66,10 @@ def delete_omics_run(context: Context, source, runUri: str = None, deleteFromAWS delete_from_aws=deleteFromAWS ) + def resolve_omics_workflow(context, source: OmicsRun, **kwargs): - return OmicsService.get_omics_workflow(source.workflowId) + return OmicsService.get_omics_workflow(source.workflowUri) def resolve_omics_run_details(context, source: OmicsRun, **kwargs): return OmicsService.get_omics_run_from_aws(source.runUri) - - diff --git a/backend/dataall/modules/omics/api/types.py b/backend/dataall/modules/omics/api/types.py index 50d38bb03..c24b3dac3 100644 --- a/backend/dataall/modules/omics/api/types.py +++ b/backend/dataall/modules/omics/api/types.py @@ -6,13 +6,14 @@ OmicsWorkflow = gql.ObjectType( name="OmicsWorkflow", fields=[ - gql.Field(name="arn", type=gql.String), + gql.Field(name="workflowUri", type=gql.String), gql.Field(name="id", type=gql.String), + gql.Field(name="arn", type=gql.String), gql.Field(name="name", type=gql.String), - gql.Field(name="status", type=gql.String), + gql.Field(name="label", type=gql.String), gql.Field(name="type", type=gql.String), gql.Field(name="description", type=gql.String), - gql.Field(name="parameterTemplate", type=gql.String), # from the omics client + gql.Field(name="parameterTemplate", type=gql.String), gql.Field(name="environmentUri", type=gql.String), ], ) @@ -29,21 +30,13 @@ ], ) -# TODO: not used at the moment -# OmicsRunStatus = gql.ObjectType( -# name="OmicsRunStatus", -# fields=[ -# gql.Field(name="arn", type=gql.String), -# gql.Field(name="id", type=gql.String), -# gql.Field(name="status", type=gql.String), -# gql.Field(name="runId", type=gql.String), -# gql.Field(name="roleArn", type=gql.String), -# gql.Field(name="statusMessage", type=gql.String), -# gql.Field(name="creationTime", type=gql.String), -# gql.Field(name="startTime", type=gql.String), -# gql.Field(name="stopTime", type=gql.String), -# ], -# ) +OmicsRunStatus = gql.ObjectType( + name="OmicsRunStatus", + fields=[ + gql.Field(name="status", type=gql.String), + gql.Field(name="statusMessage", type=gql.String) + ], +) OmicsRun = gql.ObjectType( @@ -54,17 +47,15 @@ gql.Field("organizationUri", type=gql.String), gql.Field("name", type=gql.String), gql.Field("label", type=gql.String), - gql.Field("description", type=gql.String), - gql.Field("tags", type=gql.ArrayType(gql.String)), gql.Field("created", type=gql.String), gql.Field("updated", type=gql.String), gql.Field("owner", type=gql.String), gql.Field("AwsAccountId", type=gql.String), gql.Field("region", type=gql.String), - gql.Field("workflowId", type=gql.String), + gql.Field("workflowUri", type=gql.String), gql.Field("SamlAdminGroupName", type=gql.String), gql.Field("parameterTemplate", type=gql.String), - gql.Field("outputUri", type=gql.String), + gql.Field("outputDatasetUri", type=gql.String), gql.Field( name='environment', type=gql.Ref('Environment'), @@ -100,5 +91,3 @@ gql.Field(name="nodes", type=gql.ArrayType(OmicsRun)), ], ) - - diff --git a/backend/dataall/modules/omics/aws/omics_client.py b/backend/dataall/modules/omics/aws/omics_client.py index a0cd661ba..138b802da 100644 --- a/backend/dataall/modules/omics/aws/omics_client.py +++ b/backend/dataall/modules/omics/aws/omics_client.py @@ -21,15 +21,15 @@ class OmicsClient: def client(awsAccountId: str, region: str): session = SessionHelper.remote_session(awsAccountId) return session.client('omics', region_name=region) - + @staticmethod - def get_omics_workflow(id: str, session): - workflow = OmicsRepository(session).get_workflow(id=id) + def get_omics_workflow(workflowUri: str, session): + workflow = OmicsRepository(session).get_workflow(workflowUri=workflowUri) environment = EnvironmentRepository.get_environment_by_uri(session=session, uri=workflow.environmentUri) client = OmicsClient.client(awsAccountId=environment.AwsAccountId, region=environment.region) try: response = client.get_workflow( - id=id, + id=workflow.id, type='READY2RUN' ) return response @@ -37,48 +37,47 @@ def get_omics_workflow(id: str, session): logger.error( f'Could not retrieve Ready2Run Omics Workflows status due to: {e} ' ) - return 'ERROR LISTING WORKFLOWS' + raise e @staticmethod def get_omics_run(session, runUri: str): omics_db = OmicsRepository(session) omics_run = omics_db.get_omics_run(runUri=runUri) - workflow = omics_db.get_workflow(id=omics_run.workflowId) + workflow = omics_db.get_workflow(workflowUri=omics_run.workflowUri) environment = EnvironmentRepository.get_environment_by_uri(session=session, uri=workflow.environmentUri) client = OmicsClient.client(awsAccountId=environment.AwsAccountId, region=environment.region) try: - response = client.get_run(id=omics_run.runUri - ) + response = client.get_run(id=omics_run.runUri) + # TODO: remove prints print(response) return response except ClientError as e: logger.error( f'Could not retrieve workflow run status due to: {e} ' ) - return 'ERROR GETTING WORKFLOW RUN' - + return 'ERROR GETTING WORKFLOW RUN' @staticmethod def run_omics_workflow(omics_run: OmicsRun, session): group = EnvironmentService.get_environment_group(session, omics_run.SamlAdminGroupName, omics_run.environmentUri) + workflow = OmicsRepository(session=session).get_workflow(workflowUri=omics_run.workflowUri) client = OmicsClient.client(awsAccountId=omics_run.AwsAccountId, region=omics_run.region) try: response = client.start_run( - workflowId=omics_run.workflowId, - workflowType='READY2RUN', + workflowId=workflow.id, + workflowType=workflow.type, roleArn=group.environmentIAMRoleArn, parameters=json.loads(omics_run.parameterTemplate), outputUri=omics_run.outputUri ) return response except ClientError as e: + # TODO: Check if we need to raise an error! logger.error( f'Could not retrieve workflow run status due to: {e} ' ) - return 'ERROR RUNNING OMICS WORKFLOW' - + return 'ERROR RUNNING OMICS WORKFLOW' - @staticmethod def list_workflows(awsAccountId, region, type) -> list: try: diff --git a/backend/dataall/modules/omics/cdk/omics_policy.py b/backend/dataall/modules/omics/cdk/omics_policy.py index 8fd0d75e1..a7a8afe92 100644 --- a/backend/dataall/modules/omics/cdk/omics_policy.py +++ b/backend/dataall/modules/omics/cdk/omics_policy.py @@ -1,6 +1,6 @@ from aws_cdk import aws_iam as iam -from dataall.core.environment.cdk.env_role_core_policies.service_policy import ServicePolicy +from dataall.core.environment.cdk.env_role_core_policies.service_policy import ServicePolicy from dataall.modules.omics.services.omics_permissions import CREATE_OMICS_RUN @@ -15,10 +15,10 @@ def get_statements(self, group_permissions, **kwargs): return [] return [ - iam.PolicyStatement( - actions=[ + iam.PolicyStatement( + actions=[ "omics:*" - ], - resources=['*'], - ), + ], + resources=['*'], + ), ] diff --git a/backend/dataall/modules/omics/db/models.py b/backend/dataall/modules/omics/db/models.py index 726b880dd..95cb27581 100644 --- a/backend/dataall/modules/omics/db/models.py +++ b/backend/dataall/modules/omics/db/models.py @@ -1,37 +1,27 @@ import enum from sqlalchemy import Column, String, ForeignKey -from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.orm import query_expression, relationship from dataall.base.db import Base from dataall.base.db import Resource, utils -class OmicsWorkflowType(enum.Enum): - PRIVATE = "PRIVATE" - READY2RUN = "READY2RUN" - class OmicsWorkflow(Resource, Base): __tablename__ = "omics_workflow" + workflowUri = Column(String, primary_key=True, default=utils.uuid("omicsWorkflowUri")) arn = Column(String, nullable=False) - id = Column(String, nullable=False, primary_key=True, default=utils.uuid("omicsWorkflowUri")) - label = Column(String, nullable=False, default=utils.uuid("omicsWorkflowUri")) - owner = Column(String, nullable=False, default=utils.uuid("omicsWorkflowUri")) - name = Column(String, nullable=False) - status = Column(String, nullable=False) + id = Column(String, nullable=False) type = Column(String, nullable=False) - description = Column(String, nullable=True) - environmentUri = Column(String, nullable=False) + environmentUri = Column(String, nullable=True) + class OmicsRun(Resource, Base): __tablename__ = "omics_run" runUri = Column(String, nullable=False, primary_key=True, default=utils.uuid("runUri")) organizationUri = Column(String, nullable=False) environmentUri = Column(String, ForeignKey("environment.environmentUri", ondelete="cascade"), nullable=False) - region = Column(String, default="eu-west-1") - AwsAccountId = Column(String, nullable=False) + workflowUri = Column(String, ForeignKey("omics_workflow.workflowUri", ondelete="cascade"), nullable=False) SamlAdminGroupName = Column(String, nullable=False) - workflowId = Column(String, nullable=False) parameterTemplate = Column(String, nullable=False) outputUri = Column(String, nullable=True) + outputDatasetUri = Column(String, nullable=True) diff --git a/backend/dataall/modules/omics/db/omics_repository.py b/backend/dataall/modules/omics/db/omics_repository.py index 4d0efc37e..7ac4a47d8 100644 --- a/backend/dataall/modules/omics/db/omics_repository.py +++ b/backend/dataall/modules/omics/db/omics_repository.py @@ -39,10 +39,8 @@ def delete_omics_workflow(self, omics_workflow): self._session.delete(omics_workflow) self._session.commit() - - def get_workflow(self, id: str): - return self._session.query(OmicsWorkflow).get(id) - + def get_workflow(self, workflowUri: str): + return self._session.query(OmicsWorkflow).get(workflowUri) def get_omics_run(self, runUri: str): omics_run = self._session.query(OmicsRun).get(runUri) @@ -50,7 +48,6 @@ def get_omics_run(self, runUri: str): raise exceptions.ObjectNotFound("OmicsRun", runUri) return omics_run - def _query_workflows(self, filter) -> Query: query = self._session.query(OmicsWorkflow) if filter and filter.get("term"): @@ -60,17 +57,15 @@ def _query_workflows(self, filter) -> Query: OmicsWorkflow.name.ilike(filter.get("term") + "%%"), ) ) - print(query) return query - - def paginated_omics_workflows(self,filter=None) -> dict: + + def paginated_omics_workflows(self, filter=None) -> dict: return paginate( query=self._query_workflows(filter), page=filter.get('page', OmicsRepository._DEFAULT_PAGE), page_size=filter.get('pageSize', OmicsRepository._DEFAULT_PAGE_SIZE), ).to_dict() - def _query_user_runs(self, username, groups, filter) -> Query: query = self._session.query(OmicsRun).filter( or_( @@ -87,7 +82,6 @@ def _query_user_runs(self, username, groups, filter) -> Query: ) return query - def paginated_user_runs(self, username, groups, filter=None) -> dict: return paginate( query=self._query_user_runs(username, groups, filter), @@ -97,7 +91,6 @@ def paginated_user_runs(self, username, groups, filter=None) -> dict: # TODO: IMPLEMENT COUNT_RESOUCES - # def count_resources(self, environment, group_uri): # return ( # self._session.query(OmicsRun) @@ -108,4 +101,4 @@ def paginated_user_runs(self, username, groups, filter=None) -> dict: # ) # ) # .count() - # ) \ No newline at end of file + # ) diff --git a/backend/dataall/modules/omics/services/omics_enums.py b/backend/dataall/modules/omics/services/omics_enums.py new file mode 100644 index 000000000..663b9285f --- /dev/null +++ b/backend/dataall/modules/omics/services/omics_enums.py @@ -0,0 +1,6 @@ +import enum + + +class OmicsWorkflowType(enum.Enum): + PRIVATE = "PRIVATE" + READY2RUN = "READY2RUN" diff --git a/backend/dataall/modules/omics/services/omics_service.py b/backend/dataall/modules/omics/services/omics_service.py index 0ebcb2aec..e31e57365 100644 --- a/backend/dataall/modules/omics/services/omics_service.py +++ b/backend/dataall/modules/omics/services/omics_service.py @@ -44,7 +44,6 @@ class OmicsRunCreationRequest: S3OutputBucket: str = "No output bucket provided" S3OutputPrefix: str = "" - @classmethod def from_dict(cls, env): """Copies only required fields from the dictionary and creates an instance of class""" @@ -86,12 +85,11 @@ def create_omics_run(*, uri: str, admin_group: str, data: dict) -> OmicsRun: organizationUri=environment.organizationUri, environmentUri=environment.environmentUri, SamlAdminGroupName=admin_group, - AwsAccountId=environment.AwsAccountId, - region=environment.region, - workflowId=data['workflowId'], + workflowUri=data['workflowUri'], parameterTemplate=data['parameterTemplate'], label=data['label'], - outputUri=f"s3://{environment.resourcePrefix}-{dataset.name}-{dataset.datasetUri}" + outputUri=f"s3://{dataset.S3BucketName}", + outputDatasetUri=dataset.datasetUri ) OmicsRepository(session).save_omics_run(omics_run) @@ -105,14 +103,10 @@ def create_omics_run(*, uri: str, admin_group: str, data: dict) -> OmicsRun: response = OmicsClient.run_omics_workflow(omics_run, session) - if response: - omics_run.runUri = response['id'] - OmicsRepository(session).save_omics_run(omics_run) + omics_run.runUri = response['id'] + OmicsRepository(session).save_omics_run(omics_run) - return True - # TODO: in case of failure do we want to delete the object or do we want to show it in UI? - OmicsRepository(session).delete_omics_run(omics_run) - return False + return omics_run @staticmethod @has_resource_permission(GET_OMICS_RUN) @@ -128,33 +122,26 @@ def get_omics_run_from_aws(uri: str): @staticmethod @has_tenant_permission(MANAGE_OMICS_RUNS) - def get_omics_workflow(workflowId: str) -> dict: - """List Omics workflows.""" + def get_omics_workflow(uri: str) -> dict: + """Get Omics workflow.""" with _session() as session: - response = OmicsClient.get_omics_workflow(id=workflowId, session=session) + response = OmicsClient.get_omics_workflow(workflowUri=uri, session=session) parameterTemplateJson = json.dumps(response['parameterTemplate']) response['parameterTemplate'] = parameterTemplateJson + response['workflowUri'] = uri return response - @staticmethod - @has_tenant_permission(MANAGE_OMICS_RUNS) - def run_omics_workflow(workflowId: str, workflowType: str, roleArn: str, parameters: str) -> dict: - """List Omics workflows.""" - with _session() as session: - response = OmicsClient.run_omics_workflow(workflowId,workflowType, roleArn, parameters, session) - return response - @staticmethod @has_tenant_permission(MANAGE_OMICS_RUNS) def list_user_omics_runs(filter: dict) -> dict: - """List existed user Omics pipelines. Filters only required omics_runs by the filter param""" + """List existed user Omics runs. Filters only required omics_runs by the filter param""" with _session() as session: return OmicsRepository(session).paginated_user_runs( username=get_context().username, groups=get_context().groups, filter=filter ) - + @staticmethod @has_tenant_permission(MANAGE_OMICS_RUNS) def list_omics_workflows(filter: dict) -> dict: @@ -167,8 +154,8 @@ def list_omics_workflows(filter: dict) -> dict: @staticmethod @has_resource_permission(DELETE_OMICS_RUN) def delete_omics_run(uri: str): - ## TODO: IMPLEMENT IN omics_repository - """Deletes Omics project from the database and if delete_from_aws is True from AWS as well""" + # TODO: IMPLEMENT _get_omics_run and in FRONTEND + """Deletes Omics run from the database and if delete_from_aws is True from AWS as well""" with _session() as session: omics_run = OmicsService._get_omics_run(session, uri) if not omics_run: @@ -181,5 +168,6 @@ def delete_omics_run(uri: str): group=omics_run.SamlAdminGroupName, ) + def _session(): return get_context().db_engine.scoped_session() diff --git a/backend/dataall/modules/omics/tasks/omics_workflows_fetcher.py b/backend/dataall/modules/omics/tasks/omics_workflows_fetcher.py index 5880a859d..fe6beb828 100644 --- a/backend/dataall/modules/omics/tasks/omics_workflows_fetcher.py +++ b/backend/dataall/modules/omics/tasks/omics_workflows_fetcher.py @@ -5,7 +5,8 @@ from dataall.core.environment.db.environment_models import Environment from dataall.base.db import get_engine from dataall.modules.omics.aws.omics_client import OmicsClient -from dataall.modules.omics.db.models import OmicsWorkflow, OmicsWorkflowType +from dataall.modules.omics.db.models import OmicsWorkflow +from dataall.modules.omics.services.omics_enums import OmicsWorkflowType from dataall.modules.omics.db.omics_repository import OmicsRepository @@ -18,7 +19,7 @@ def fetch_omics_workflows(engine): """List Omics workflows.""" - log.info(f'Starting omics workflows fetcher') + log.info('Starting omics workflows fetcher') with engine.scoped_session() as session: environments = session.query(Environment) is_first_time = True @@ -46,6 +47,7 @@ def fetch_omics_workflows(engine): is_first_time = False return True + if __name__ == '__main__': ENVNAME = os.environ.get('envname', 'local') ENGINE = get_engine(envname=ENVNAME) diff --git a/backend/migrations/versions/9337b882dbb8_add_omics_runs.py b/backend/migrations/versions/9337b882dbb8_add_omics_runs.py index eab83df11..fc4a9708b 100644 --- a/backend/migrations/versions/9337b882dbb8_add_omics_runs.py +++ b/backend/migrations/versions/9337b882dbb8_add_omics_runs.py @@ -39,5 +39,6 @@ def upgrade(): sa.PrimaryKeyConstraint('runUri', name='omics_run_pkey'), ) + def downgrade(): op.drop_table('omics_run') diff --git a/frontend/src/design/components/layout/DefaultSidebar.js b/frontend/src/design/components/layout/DefaultSidebar.js index ed064e08a..143c7379f 100644 --- a/frontend/src/design/components/layout/DefaultSidebar.js +++ b/frontend/src/design/components/layout/DefaultSidebar.js @@ -13,6 +13,7 @@ import { AiOutlineExperiment } from 'react-icons/ai'; import * as BiIcons from 'react-icons/bi'; import * as BsIcons from 'react-icons/bs'; import { FiCodesandbox, FiPackage } from 'react-icons/fi'; +import { FaDna } from 'react-icons/fa6'; import { MdShowChart } from 'react-icons/md'; import { SiJupyter } from 'react-icons/si'; import { VscBook } from 'react-icons/vsc'; @@ -90,7 +91,7 @@ export const DefaultSidebar = ({ openDrawer, onOpenDrawerChange }) => { const omicsSection = { title: 'Omics', path: '/console/omics', - icon: , + icon: , active: isModuleEnabled(ModuleNames.OMICS) }; diff --git a/frontend/src/modules/Environments/views/EnvironmentCreateForm.js b/frontend/src/modules/Environments/views/EnvironmentCreateForm.js index cde6968ef..0a6bbf493 100644 --- a/frontend/src/modules/Environments/views/EnvironmentCreateForm.js +++ b/frontend/src/modules/Environments/views/EnvironmentCreateForm.js @@ -199,6 +199,10 @@ const EnvironmentCreateForm = (props) => { { key: 'pipelinesEnabled', value: String(values.pipelinesEnabled) + }, + { + key: 'omicsEnabled', + value: String(values.omicsEnabled) } ] }) @@ -506,6 +510,7 @@ const EnvironmentCreateForm = (props) => { notebooksEnabled: isModuleEnabled(ModuleNames.NOTEBOOKS), mlStudiosEnabled: isModuleEnabled(ModuleNames.MLSTUDIO), pipelinesEnabled: isModuleEnabled(ModuleNames.DATAPIPELINES), + omicsEnabled: isModuleEnabled(ModuleNames.OMICS), EnvironmentDefaultIAMRoleArn: '', resourcePrefix: 'dataall', vpcId: '', @@ -772,6 +777,39 @@ const EnvironmentCreateForm = (props) => { )} + {isModuleEnabled(ModuleNames.OMICS) && ( + + + + } + label={ + + Omics{' '} + + (Requires AWS HealthOmics) + + + } + labelPlacement="end" + value={values.omicsEnabled} + /> + + + )} )} diff --git a/frontend/src/modules/Omics/views/OmicsRunsList.js b/frontend/src/modules/Omics/components/OmicsRunsList.js similarity index 100% rename from frontend/src/modules/Omics/views/OmicsRunsList.js rename to frontend/src/modules/Omics/components/OmicsRunsList.js diff --git a/frontend/src/modules/Omics/components/OmicsWorkflowDetails.js b/frontend/src/modules/Omics/components/OmicsWorkflowDetails.js index 477d0cc3a..8019bf2c2 100644 --- a/frontend/src/modules/Omics/components/OmicsWorkflowDetails.js +++ b/frontend/src/modules/Omics/components/OmicsWorkflowDetails.js @@ -7,7 +7,7 @@ export const OmicsWorkflowDetails = (props) => { return ( - + { /> - + diff --git a/frontend/src/modules/Omics/components/OmicsWorkflowsList.js b/frontend/src/modules/Omics/components/OmicsWorkflowsList.js new file mode 100644 index 000000000..fd9278b06 --- /dev/null +++ b/frontend/src/modules/Omics/components/OmicsWorkflowsList.js @@ -0,0 +1,98 @@ +import { Box, Container, Typography } from '@mui/material'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useCallback, useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet-async'; +import { Defaults, Pager, useSettings } from 'design'; +import { SET_ERROR, useDispatch } from 'globalErrors'; +import { useClient } from 'services'; +import { listOmicsWorkflows } from '../services'; +import { OmicsWorkflowsListItem } from './OmicsWorkflowsListItem'; + +export const OmicsWorkflowsList = () => { + const dispatch = useDispatch(); + const [items, setItems] = useState(Defaults.pagedResponse); + const [filter, setFilter] = useState(Defaults.filter); + const { settings } = useSettings(); + // const [inputValue, setInputValue] = useState(''); + const [loading, setLoading] = useState(true); + const client = useClient(); + + // const handleInputChange = (event) => { + // setInputValue(event.target.value); + // setFilter({ ...filter, term: event.target.value }); + // }; + // + // const handleInputKeyup = (event) => { + // if (event.code === 'Enter') { + // setFilter({ page: 1, term: event.target.value }); + // fetchItems().catch((e) => + // dispatch({ type: SET_ERROR, error: e.message }) + // ); + // } + // }; + const handlePageChange = async (event, value) => { + if (value <= items.pages && value !== items.page) { + await setFilter({ ...filter, page: value }); + } + }; + + const fetchItems = useCallback(async () => { + setLoading(true); + const response = await client.query(listOmicsWorkflows(filter)); + if (!response.errors) { + setItems(response.data.listOmicsWorkflows); + } else { + dispatch({ type: SET_ERROR, error: response.errors[0].message }); + } + setLoading(false); + }, [client, dispatch, filter]); + + useEffect(() => { + if (client) { + fetchItems().catch((e) => + dispatch({ type: SET_ERROR, error: e.message }) + ); + } + }, [client, filter.page, dispatch, fetchItems]); + + if (loading) { + return ; + } + + return ( + <> + + Workflows | data.all + + + + + {items.nodes.length <= 0 ? ( + + No workflows registered in data.all. + + ) : ( + + {items.nodes.map((node) => ( + + ))} + + + + )} + + + + + ); +}; diff --git a/frontend/src/modules/Omics/components/OmicsWorkflowsListItem.js b/frontend/src/modules/Omics/components/OmicsWorkflowsListItem.js index b11dfd8c4..bf843570c 100644 --- a/frontend/src/modules/Omics/components/OmicsWorkflowsListItem.js +++ b/frontend/src/modules/Omics/components/OmicsWorkflowsListItem.js @@ -1,148 +1,93 @@ -// TODO: completely -import { - Box, - Button, - Card, - Divider, - Grid, - Link, - Tooltip, - Typography -} from '@mui/material'; -import * as FiIcons from 'react-icons/fi'; -import { Link as RouterLink } from 'react-router-dom'; +import { Box, Button, Card, Grid, Typography } from '@mui/material'; import PropTypes from 'prop-types'; -import { useNavigate } from 'react-router'; -import { FiCodesandbox } from 'react-icons/fi'; -import React from 'react'; - -import { IconAvatar, StackStatus, useCardStyle } from 'design'; +import { useCardStyle } from 'design'; +import { Link as RouterLink } from 'react-router-dom'; -export const OmicsWorkflowsListItem = (props) => { - const { workflow } = props; +export const OmicsWorkflowsListItem = ({ workflow }) => { const classes = useCardStyle(); - const navigate = useNavigate(); + return ( - - - + + + + + + Workflow Id + + + {`${workflow.id}`} + + + + - } /> - - { - navigate(`/console/omics/workflows/${workflow.id}`); - }} - sx={{ - width: '99%', - whiteSpace: 'nowrap', - alignItems: 'left', - overflow: 'hidden', - textOverflow: 'ellipsis', - WebkitBoxOrient: 'vertical', - WebkitLineClamp: 2 - }} - > - - Workflow id: {workflow.id} - - - - - {workflow.name} - - - + + Name + + + {`${workflow.name}`} + - - - - - - Type - - - - - {workflow.type} - - - - - - - - - Status - - - - - - - - - - - - - - + + - + {`${workflow.type}`} + - - - + + + + + + ); }; OmicsWorkflowsListItem.propTypes = { diff --git a/frontend/src/modules/Omics/components/index.js b/frontend/src/modules/Omics/components/index.js index be02a0c9d..3464f4436 100644 --- a/frontend/src/modules/Omics/components/index.js +++ b/frontend/src/modules/Omics/components/index.js @@ -1,2 +1,4 @@ -export * from './OmicsWorkflowsListItem'; +export * from './OmicsRunsList'; export * from './OmicsWorkflowDetails'; +export * from './OmicsWorkflowsList'; +export * from './OmicsWorkflowsListItem'; diff --git a/frontend/src/modules/Omics/services/getOmicsWorkflow.js b/frontend/src/modules/Omics/services/getOmicsWorkflow.js index 922948cfb..f73f24c94 100644 --- a/frontend/src/modules/Omics/services/getOmicsWorkflow.js +++ b/frontend/src/modules/Omics/services/getOmicsWorkflow.js @@ -1,17 +1,17 @@ import { gql } from 'apollo-boost'; // TODO: add output fields -export const getOmicsWorkflow = (workflowId) => ({ +export const getOmicsWorkflow = (workflowUri) => ({ variables: { - workflowId + workflowUri }, query: gql` - query getOmicsWorkflow($workflowId: String!) { - getOmicsWorkflow(workflowId: $workflowId) { + query getOmicsWorkflow($workflowUri: String!) { + getOmicsWorkflow(workflowUri: $workflowUri) { + workflowUri id name description parameterTemplate - status type } } diff --git a/frontend/src/modules/Omics/services/listOmicsRuns.js b/frontend/src/modules/Omics/services/listOmicsRuns.js index 45fdb6c40..919ea9bd4 100644 --- a/frontend/src/modules/Omics/services/listOmicsRuns.js +++ b/frontend/src/modules/Omics/services/listOmicsRuns.js @@ -14,11 +14,11 @@ export const listOmicsRuns = (filter) => ({ hasPrevious nodes { runUri - workflowId + workflowUri name owner SamlAdminGroupName - outputUri + outputDatasetUri description label created @@ -37,23 +37,17 @@ export const listOmicsRuns = (filter) => ({ organizationUri } workflow { - id + label name + workflowUri + id description parameterTemplate - status type } status { - arn - id status - runId - roleArn statusMessage - creationTime - startTime - stopTime } } } diff --git a/frontend/src/modules/Omics/services/listOmicsWorkflows.js b/frontend/src/modules/Omics/services/listOmicsWorkflows.js index a6deb77ff..695586317 100644 --- a/frontend/src/modules/Omics/services/listOmicsWorkflows.js +++ b/frontend/src/modules/Omics/services/listOmicsWorkflows.js @@ -15,10 +15,11 @@ export const listOmicsWorkflows = (filter) => ({ hasPrevious nodes { arn - name id + name + label + workflowUri description - status type parameterTemplate } diff --git a/frontend/src/modules/Omics/views/OmicsList.js b/frontend/src/modules/Omics/views/OmicsList.js new file mode 100644 index 000000000..a5095b821 --- /dev/null +++ b/frontend/src/modules/Omics/views/OmicsList.js @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; +import { Helmet } from 'react-helmet-async'; +import { + Box, + Container, + Divider, + Grid, + Tab, + Tabs, + Typography +} from '@mui/material'; +import { FaDna, FaGear } from 'react-icons/fa6'; +import { useSettings } from 'design'; + +import { OmicsWorkflowsList, OmicsRunList } from '../components'; + +const tabs = [ + { label: 'Workflows', value: 'workflows', icon: }, + { label: 'Runs', value: 'runs', icon: } +]; + +const OmicsList = () => { + const { settings } = useSettings(); + const [currentTab, setCurrentTab] = useState('workflows'); + + const handleTabsChange = (event, value) => { + setCurrentTab(value); + }; + + return ( + <> + + Omics | data.all + + + + + + + Omics + + + + + + {tabs.map((tab) => ( + + ))} + + + + + {currentTab === 'workflows' && } + {currentTab === 'runs' && } + + + + + ); +}; + +export default OmicsList; diff --git a/frontend/src/modules/Omics/views/OmicsListView.js b/frontend/src/modules/Omics/views/OmicsListView.js deleted file mode 100644 index f2f121f18..000000000 --- a/frontend/src/modules/Omics/views/OmicsListView.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useState } from 'react'; -import { Helmet } from 'react-helmet-async'; -import { Box, Container, Divider, Tab, Tabs } from '@mui/material'; -import { FaAws } from 'react-icons/fa'; -import { Info } from '@mui/icons-material'; -import { useSettings } from 'design'; - -import { OmicsRunList } from './OmicsRunsList'; -import { OmicsWorkflowsList } from './OmicsWorkflowsList'; -//TODO: mostly done, but review -const tabs = [ - { label: 'Workflows', value: 'workflows', icon: }, - { label: 'Runs', value: 'runs', icon: } -]; - -const OmicsView = () => { - const { settings } = useSettings(); - const [currentTab, setCurrentTab] = useState('workflows'); - - const handleTabsChange = (event, value) => { - setCurrentTab(value); - }; - - return ( - <> - - Omics - - - - {tabs.map((tab) => ( - - ))} - - - - - - - {currentTab === 'workflows' && } - {currentTab === 'runs' && } - - - - - ); -}; - -export default OmicsView; diff --git a/frontend/src/modules/Omics/views/OmicsRunCreateForm.js b/frontend/src/modules/Omics/views/OmicsRunCreateForm.js index 74415c46c..d45c2c472 100644 --- a/frontend/src/modules/Omics/views/OmicsRunCreateForm.js +++ b/frontend/src/modules/Omics/views/OmicsRunCreateForm.js @@ -48,7 +48,7 @@ const OmicsRunCreateForm = (props) => { const [loading, setLoading] = useState(true); const fetchItem = useCallback(async () => { setLoading(true); - const response = await client.query(getOmicsWorkflow(params.workflowId)); + const response = await client.query(getOmicsWorkflow(params.uri)); if (!response.errors) { setOmicsWorkflow(response.data.getOmicsWorkflow); console.log(omicsWorkflow); // eslint-disable-line no-console @@ -59,7 +59,7 @@ const OmicsRunCreateForm = (props) => { dispatch({ type: SET_ERROR, error }); } setLoading(false); - }, [client, dispatch, params.workflowId]); + }, [client, dispatch, params.uri]); const [groupOptions, setGroupOptions] = useState([]); const [environmentOptions, setEnvironmentOptions] = useState([]); @@ -155,7 +155,7 @@ const OmicsRunCreateForm = (props) => { createOmicsRun({ label: values.label, environmentUri: values.environment.environmentUri, - workflowId: params.workflowId, + workflowUri: omicsWorkflow.workflowUri, parameterTemplate: values.parameterTemplate, SamlAdminGroupName: values.SamlAdminGroupName, destination: values.destination @@ -255,7 +255,7 @@ const OmicsRunCreateForm = (props) => { { parameterTemplate: omicsWorkflow.parameterTemplate }} validationSchema={Yup.object().shape({ - omicsWorkflowId: Yup.string() + workflowUri: Yup.string() .max(255) .required('*Workflow is required'), label: Yup.string().max(255).required('*Run Name is required'), @@ -302,17 +302,11 @@ const OmicsRunCreateForm = (props) => { diff --git a/frontend/src/modules/Omics/views/OmicsWorkflowParameters.js b/frontend/src/modules/Omics/views/OmicsWorkflowParameters.js deleted file mode 100644 index aa7a8f52e..000000000 --- a/frontend/src/modules/Omics/views/OmicsWorkflowParameters.js +++ /dev/null @@ -1,42 +0,0 @@ -import { Box, Grid } from '@mui/material'; -import PropTypes from 'prop-types'; -import { ObjectBrief, ObjectMetadata } from 'design'; - -const OmicsWorkflowDetails = (props) => { - const { workflow, ...other } = props; - - return ( - - - - 0 ? workflow.tags : ['-'] - } - /> - - - - - - - ); -}; - -OmicsWorkflowDetails.propTypes = { - workflow: PropTypes.object.isRequired -}; - -export default OmicsWorkflowDetails; diff --git a/frontend/src/modules/Omics/views/OmicsWorkflowView.js b/frontend/src/modules/Omics/views/OmicsWorkflowView.js index 6f54bb09e..07cfeb353 100644 --- a/frontend/src/modules/Omics/views/OmicsWorkflowView.js +++ b/frontend/src/modules/Omics/views/OmicsWorkflowView.js @@ -33,7 +33,7 @@ const OmicsWorkflowView = () => { } else { const error = response.errors ? response.errors[0].message - : 'Omics Workflownot found'; + : 'Omics Workflow not found'; dispatch({ type: SET_ERROR, error }); } setLoading(false); @@ -65,26 +65,29 @@ const OmicsWorkflowView = () => { }} > - - + + - Omics Workflow: {omicsWorkflow.name} + Omics Workflow + + + {omicsWorkflow.name} - - - - - + + + + + diff --git a/frontend/src/modules/Omics/views/OmicsWorkflowsList.js b/frontend/src/modules/Omics/views/OmicsWorkflowsList.js deleted file mode 100644 index e9ec1fa46..000000000 --- a/frontend/src/modules/Omics/views/OmicsWorkflowsList.js +++ /dev/null @@ -1,155 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { - Box, - Breadcrumbs, - Container, - Grid, - Link, - Typography -} from '@mui/material'; -import CircularProgress from '@mui/material/CircularProgress'; -import { Helmet } from 'react-helmet-async'; - -import { useClient } from 'services'; -import { - ChevronRightIcon, - Defaults, - Pager, - SearchInput, - useSettings -} from 'design'; -import { SET_ERROR, useDispatch } from 'globalErrors'; -import { listOmicsWorkflows } from '../services'; -import { OmicsWorkflowsListItem } from '../components'; - -function OmicsPageHeader() { - return ( - - - - Workflows - - } - sx={{ mt: 1 }} - > - - Play - - - Workflows - - - - - ); -} - -export const OmicsWorkflowsList = () => { - const dispatch = useDispatch(); - const [items, setItems] = useState(Defaults.pagedResponse); - const [filter, setFilter] = useState(Defaults.filter); - const { settings } = useSettings(); - const [inputValue, setInputValue] = useState(''); - const [loading, setLoading] = useState(true); - const client = useClient(); - - const fetchItems = useCallback(async () => { - setLoading(true); - const response = await client.query(listOmicsWorkflows(filter)); - if (!response.errors) { - setItems(response.data.listOmicsWorkflows); - } else { - dispatch({ type: SET_ERROR, error: response.errors[0].message }); - } - setLoading(false); - }, [client, dispatch, filter]); - - const handleInputChange = (event) => { - setInputValue(event.target.value); - setFilter({ ...filter, term: event.target.value }); - }; - - const handleInputKeyup = (event) => { - if (event.code === 'Enter') { - setFilter({ page: 1, term: event.target.value }); - fetchItems().catch((e) => - dispatch({ type: SET_ERROR, error: e.message }) - ); - } - }; - - const handlePageChange = async (event, value) => { - if (value <= items.pages && value !== items.page) { - await setFilter({ ...filter, page: value }); - } - }; - - useEffect(() => { - if (client) { - fetchItems().catch((e) => - dispatch({ type: SET_ERROR, error: e.message }) - ); - } - }, [client, filter.page, dispatch, fetchItems]); - - return ( - <> - - Workflows | data.all - - - - - - - - - - {loading ? ( - - ) : ( - - - {items.nodes.map((node) => ( - - ))} - - - - - )} - - - - - ); -}; diff --git a/frontend/src/modules/index.js b/frontend/src/modules/index.js index 873e5177d..5599e9abd 100644 --- a/frontend/src/modules/index.js +++ b/frontend/src/modules/index.js @@ -5,6 +5,7 @@ export * from './Glossaries'; export * from './MLStudio'; export * from './Notebooks'; export * from './Notifications'; +export * from './Omics'; export * from './Pipelines'; export * from './Shares'; export * from './Worksheets'; diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 8852233b7..db9fb8ad3 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -154,17 +154,13 @@ const GlossaryCreateForm = Loadable( ); const OmicsList = Loadable( - lazy(() => import('./modules/Omics/views/OmicsListView')) + lazy(() => import('./modules/Omics/views/OmicsList')) ); -const OmicsWorkflowsList = Loadable( - lazy(() => import('./modules/Omics/views/OmicsWorkflowsList')) -); -const OmicsWorkflowsView = Loadable( + +const OmicsWorkflowView = Loadable( lazy(() => import('./modules/Omics/views/OmicsWorkflowView')) ); -const OmicsRunsList = Loadable( - lazy(() => import('./modules/Omics/views/OmicsRunsList')) -); + const OmicsRunCreateForm = Loadable( lazy(() => import('./modules/Omics/views/OmicsRunCreateForm')) ); @@ -416,20 +412,12 @@ const routes = [ path: 'omics', element: }, - { - path: 'omics/workflows', - element: - }, - { - path: 'omics/runs', - element: - }, { path: 'omics/workflows/:uri', - element: + element: }, { - path: 'omics/runs/new/:workflowId', + path: 'omics/workflows/:uri/runs/new', element: } ]