diff --git a/graphql_api/schema/custom/automation_task.py b/graphql_api/schema/custom/automation_task.py index 3a73e47..7a20094 100644 --- a/graphql_api/schema/custom/automation_task.py +++ b/graphql_api/schema/custom/automation_task.py @@ -46,8 +46,9 @@ class Meta: model_type = ModelType() task_type = TaskSubType() inversion_solution = graphene.Field( - 'graphql_api.schema.custom.inversion_solution.InversionSolution', - description="the primary result of this task (only for task_type == INVERSION.", + 'graphql_api.schema.custom.inversion_solution_union.InversionSolutionUnion', + description="the result of this task. NB only available for task_types:" + "INVERSION, SCALE_SOLUTION, AGGREGATE_SOLUTION, TIME_DEPENDENT_SOLUTION.", ) @staticmethod @@ -56,16 +57,27 @@ def from_json(jsondata): @staticmethod def resolve_inversion_solution(root, info, **args): + + log.info(f"resolve_inversion_solution {root.task_type}") + resolvable_types = [ + TaskSubType.INVERSION.value, + TaskSubType.SCALE_SOLUTION.value, + TaskSubType.AGGREGATE_SOLUTION.value, + TaskSubType.TIME_DEPENDENT_SOLUTION.value, + ] + if not len(root.files): return - if not root.task_type == TaskSubType.INVERSION.value: + if root.task_type not in resolvable_types: + log.info(f"Cannot resove inversion_soluton for {root.task_type}") return t0 = dt.utcnow() res = None # TODO this is an ugly hack.... - # - It gets the inversion solution by traversing the file_relations until it finds an InversionSolution. + # - It gets the inversion solution by traversing the file_relations until it finds + # an InversionSolution subtype. # - Instead this attribute needs to be a first-class one-to-one relationship for file_id in root.files: if isinstance(file_id, dict): # new form, files is list of objects @@ -79,9 +91,11 @@ def resolve_inversion_solution(root, info, **args): if not file_relation.role == FileRole.WRITE.value: continue file = get_data_manager().file.get_one(file_relation.file_id) - if file.__class__.__name__ == 'InversionSolution': + if 'InversionSolution' in file.__class__.__name__: res = file + log.info(f"resolved inversion_solution file {file}") break + db_metrics.put_duration(__name__, 'AutomationTask.resolve_inversion_solution', dt.utcnow() - t0) return res diff --git a/graphql_api/schema/custom/inversion_solution_union.py b/graphql_api/schema/custom/inversion_solution_union.py new file mode 100644 index 0000000..9e4c782 --- /dev/null +++ b/graphql_api/schema/custom/inversion_solution_union.py @@ -0,0 +1,17 @@ +# !inversion_solution_union.py +import graphene + +from .aggregate_inversion_solution import AggregateInversionSolution +from .inversion_solution import InversionSolution +from .scaled_inversion_solution import ScaledInversionSolution +from .time_dependent_inversion_solution import TimeDependentInversionSolution + + +class InversionSolutionUnion(graphene.Union): + class Meta: + types = ( + InversionSolution, + ScaledInversionSolution, + AggregateInversionSolution, + TimeDependentInversionSolution, + ) diff --git a/graphql_api/tests/legacy/test_automation_task_related_solution.py b/graphql_api/tests/legacy/test_automation_task_related_solution.py index e9f912f..acfde81 100644 --- a/graphql_api/tests/legacy/test_automation_task_related_solution.py +++ b/graphql_api/tests/legacy/test_automation_task_related_solution.py @@ -140,8 +140,8 @@ def test_task_product_query(self, mocked_api_DB): id created inversion_solution { - id - file_name + ... on Node { id } + ... on FileInterface { file_name } } files { total_count @@ -183,8 +183,8 @@ def test_example_failing_product_query(self, mocked_api_DB): id created inversion_solution { - id - file_name + ... on Node { id } + ... on FileInterface { file_name } } files { total_count diff --git a/graphql_api/tests/simpler_relationships/test_automation_task_related_solution_new.py b/graphql_api/tests/simpler_relationships/test_automation_task_related_solution_new.py index 95ecad1..127caba 100644 --- a/graphql_api/tests/simpler_relationships/test_automation_task_related_solution_new.py +++ b/graphql_api/tests/simpler_relationships/test_automation_task_related_solution_new.py @@ -115,8 +115,8 @@ def test_task_product_query(self, mocked_api): id created inversion_solution { - id - file_name + ... on Node { id } + ... on FileInterface { file_name } } files { total_count diff --git a/graphql_api/tests/test_automation_task inversion_solution_resolves_subtypes.py b/graphql_api/tests/test_automation_task inversion_solution_resolves_subtypes.py new file mode 100644 index 0000000..e4f6f1b --- /dev/null +++ b/graphql_api/tests/test_automation_task inversion_solution_resolves_subtypes.py @@ -0,0 +1,131 @@ +""" +Test API function for GeneralTask +using moto mocking re issue #223 +""" + +import datetime as dt +import unittest + +import boto3 +from dateutil.tz import tzutc +from graphene.test import Client +from graphql_relay import from_global_id +from moto import mock_dynamodb, mock_s3 +from pynamodb.connection.base import Connection # for mocking + +from graphql_api.config import REGION, S3_BUCKET_NAME +from graphql_api.data import data_manager +from graphql_api.dynamodb.models import ToshiFileObject, ToshiIdentity, ToshiThingObject +from graphql_api.schema import root_schema +from graphql_api.schema.search_manager import SearchManager + +from .hazard.setup_helpers import SetupHelpersMixin + + +@mock_dynamodb +@mock_s3 +class TestScaledInversionSolution(unittest.TestCase, SetupHelpersMixin): + def setUp(self): + self.client = Client(root_schema) + + # S3 + self._s3 = boto3.resource('s3', region_name=REGION) + self._s3.create_bucket(Bucket=S3_BUCKET_NAME) + + # Dynamo + self._connection = Connection(region=REGION) + + ToshiThingObject.create_table() + ToshiFileObject.create_table() + ToshiIdentity.create_table() + + self._data_manager = data_manager.DataManager(search_manager=SearchManager('test', 'test', {'fake': 'auth'})) + + upstream_sid = self.create_source_solution() + self.new_gt = self.create_general_task() + self.at_id = self.create_automation_task("SCALE_SOLUTION") + self.create_gt_relation(self.new_gt, self.at_id) + + result = self.create_scaled_solution(upstream_sid, self.at_id) + + ss = result['data']['create_scaled_inversion_solution']['solution'] + self.scaled_solution_id = ss['id'] + + # def create_task_file(self, task_id, file_id, role): + qry2 = ''' + mutation create_file_relation( + $thing_id:ID! + $file_id:ID! + $role:FileRole!) { + create_file_relation( + file_id:$file_id + thing_id:$thing_id + role:$role + ) + { + ok + } + }''' + variables = dict(thing_id=self.at_id, file_id=self.at_id, role='WRITE') + executed = self.client.execute(qry2, variable_values=variables) + print('created file relation', executed) + + def test_general_task_query(self): + print("self.new_gt", self.new_gt) + + qry = ''' + query GeneralTaskChildrenTabQuery($id: ID!) { + node(id: $id) { + ... on GeneralTask { + id + model_type + children { + edges { + node { + child { + __typename + ... on Node { + id + } + ...on AutomationTask { + task_type + inversion_solution { + __typename + ... on Node { + id + } + } + } + ... on AutomationTaskInterface { + state + result + created + duration + arguments { + k + v + } + } + } + } + } + } + } + } + } + + ''' + + print(qry) + executed = self.client.execute(qry, variable_values=dict(id=self.new_gt)) + print(executed) + + node = executed['data']['node'] + assert node['id'] == self.new_gt + assert node['children']['edges'][0]['node']['child']['__typename'] == "AutomationTask" + assert node['children']['edges'][0]['node']['child']['inversion_solution']['__typename'] + assert node['children']['edges'][0]['node']['child']['inversion_solution']['id'] == self.scaled_solution_id + assert ( + node['children']['edges'][0]['node']['child']['inversion_solution']['__typename'] + == "ScaledInversionSolution" + ) diff --git a/graphql_api/tests/test_inversion_solution_bug_93.py b/graphql_api/tests/test_inversion_solution_bug_93.py index 9638f0a..80f381a 100644 --- a/graphql_api/tests/test_inversion_solution_bug_93.py +++ b/graphql_api/tests/test_inversion_solution_bug_93.py @@ -148,15 +148,19 @@ def test_query_with_files(self, mocked_read_object, mocked_get_one): task_type id inversion_solution { - id - file_name - meta { - k - v + ... on Node { id } + ... on FileInterface { + file_name + meta { + k + v + } } - tables { - table_id - table_type + ... on InversionSolutionInterface { + tables { + table_id + table_type + } } } }