From e137bd999593fddbf8acc33b4347cf76cf1fe7de Mon Sep 17 00:00:00 2001 From: tjeerddie Date: Mon, 21 Aug 2023 16:32:08 +0200 Subject: [PATCH] Add depends_on and in_use_by to product block graphql type (#336) * Add depends_on and in_use_by to product block graphql type * Bump version to 1.2.3rc5 * Fix ruff issues * add helper function for tests to assert assert no difference - uses deepdiff with its exclude_paths. - change product_blocks graphql schema to use list instead of List --------- Co-authored-by: Tjeerd.Verschragen --- .bumpversion.cfg | 2 +- orchestrator/__init__.py | 2 +- orchestrator/graphql/schemas/product_block.py | 12 +- orchestrator/utils/helpers.py | 2 +- .../managers/broadcast_websocket_manager.py | 2 +- test/unit_tests/conftest.py | 3 + .../unit_tests/graphql/test_product_blocks.py | 108 +++++++++++++++++- test/unit_tests/helpers.py | 10 ++ 8 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 test/unit_tests/helpers.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f6d4a4209..3f092cc93 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.2.3rc4 +current_version = 1.2.3rc5 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(rc(?P\d+))? diff --git a/orchestrator/__init__.py b/orchestrator/__init__.py index d6a42f022..b521d60a2 100644 --- a/orchestrator/__init__.py +++ b/orchestrator/__init__.py @@ -13,7 +13,7 @@ """This is the orchestrator workflow engine.""" -__version__ = "1.2.3rc4" +__version__ = "1.2.3rc5" from orchestrator.app import OrchestratorCore from orchestrator.settings import app_settings, oauth2_settings diff --git a/orchestrator/graphql/schemas/product_block.py b/orchestrator/graphql/schemas/product_block.py index 8459066f8..eac0505f2 100644 --- a/orchestrator/graphql/schemas/product_block.py +++ b/orchestrator/graphql/schemas/product_block.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Annotated import strawberry @@ -15,4 +15,12 @@ class ProductBlock: status: strawberry.auto created_at: strawberry.auto end_date: strawberry.auto - resource_types: Optional[list[ResourceType]] + resource_types: list[ResourceType] + + @strawberry.field(description="Return all product blocks that this product block depends on") # type: ignore + async def depends_on(self) -> list[Annotated["ProductBlock", strawberry.lazy(".product_block")]]: + return [ProductBlock.from_pydantic(product_block) for product_block in self._original_model.depends_on] # type: ignore + + @strawberry.field(description="Return all product blocks that uses this product block") # type: ignore + async def in_use_by(self) -> list[Annotated["ProductBlock", strawberry.lazy(".product_block")]]: + return [ProductBlock.from_pydantic(product_block) for product_block in self._original_model.in_use_by] # type: ignore diff --git a/orchestrator/utils/helpers.py b/orchestrator/utils/helpers.py index d44b50f91..0183d25d9 100644 --- a/orchestrator/utils/helpers.py +++ b/orchestrator/utils/helpers.py @@ -75,7 +75,7 @@ def map_value(mapping: dict[str, Callable], k: str, v: Any) -> tuple[Any, ...]: if f := mapping.get(k): if v is None: return k, None - if type(v) == dict: + if isinstance(v, dict): return result if type(result := f(**v)) is tuple else (k, result) return result if type(result := f(v)) is tuple else (k, result) return k, v diff --git a/orchestrator/websocket/managers/broadcast_websocket_manager.py b/orchestrator/websocket/managers/broadcast_websocket_manager.py index f64956f49..262de44a8 100644 --- a/orchestrator/websocket/managers/broadcast_websocket_manager.py +++ b/orchestrator/websocket/managers/broadcast_websocket_manager.py @@ -70,7 +70,7 @@ async def sender(self, websocket: WebSocket, channel: str) -> None: await websocket.send_text(event.message) json = json_loads(event.message) - if type(json) is dict and "close" in json and json["close"] and channel != "processes": + if isinstance(json, dict) and "close" in json and json["close"] and channel != "processes": await self.disconnect(websocket) break diff --git a/test/unit_tests/conftest.py b/test/unit_tests/conftest.py index 8567671da..676259517 100644 --- a/test/unit_tests/conftest.py +++ b/test/unit_tests/conftest.py @@ -381,6 +381,7 @@ def generic_product_block_1(generic_resource_type_1): tag="PB1", status="active", resource_types=[generic_resource_type_1], + created_at=datetime.datetime.fromisoformat("2023-05-24T00:00:00+00:00"), ) db.session.add(pb) db.session.commit() @@ -395,6 +396,7 @@ def generic_product_block_2(generic_resource_type_2, generic_resource_type_3): tag="PB2", status="active", resource_types=[generic_resource_type_2, generic_resource_type_3], + created_at=datetime.datetime.fromisoformat("2023-05-24T00:00:00+00:00"), ) db.session.add(pb) db.session.commit() @@ -409,6 +411,7 @@ def generic_product_block_3(generic_resource_type_2): tag="PB3", status="active", resource_types=[generic_resource_type_2], + created_at=datetime.datetime.fromisoformat("2023-05-24T00:00:00+00:00"), ) db.session.add(pb) db.session.commit() diff --git a/test/unit_tests/graphql/test_product_blocks.py b/test/unit_tests/graphql/test_product_blocks.py index ca084c8b8..d01a91ea5 100644 --- a/test/unit_tests/graphql/test_product_blocks.py +++ b/test/unit_tests/graphql/test_product_blocks.py @@ -4,6 +4,8 @@ from fastapi import Response +from test.unit_tests.helpers import assert_no_diff + def get_product_blocks_query( first: int = 10, @@ -16,17 +18,23 @@ def get_product_blocks_query( productBlocks(first: $first, after: $after, filterBy: $filterBy, sortBy: $sortBy) { page { name - productBlockId endDate description createdAt status tag resourceTypes { - resourceTypeId resourceType description } + dependsOn { + name + description + } + inUseBy { + name + description + } } pageInfo { endCursor @@ -72,6 +80,102 @@ def test_product_blocks_query(test_client): "totalItems": 6, } + expected = [ + { + "name": "PB_1", + "endDate": None, + "description": "Generic Product Block 1", + "createdAt": "2023-05-24T00:00:00+00:00", + "status": "ACTIVE", + "tag": "PB1", + "resourceTypes": [{"resourceType": "rt_1", "description": "Resource Type one"}], + "dependsOn": [], + "inUseBy": [], + }, + { + "name": "PB_2", + "endDate": None, + "description": "Generic Product Block 2", + "createdAt": "2023-05-24T00:00:00+00:00", + "status": "ACTIVE", + "tag": "PB2", + "resourceTypes": [ + {"resourceType": "rt_2", "description": "Resource Type two"}, + {"resourceType": "rt_3", "description": "Resource Type three"}, + ], + "dependsOn": [], + "inUseBy": [], + }, + ] + assert product_blocks == expected + + +def test_product_block_query_with_relations(test_client): + data = get_product_blocks_query(filter_by={"field": "name", "value": "ForTest"}) + response: Response = test_client.post("/api/graphql", content=data, headers={"Content-Type": "application/json"}) + + assert HTTPStatus.OK == response.status_code + result = response.json() + product_blocks_data = result["data"]["productBlocks"] + product_blocks = product_blocks_data["page"] + pageinfo = product_blocks_data["pageInfo"] + + assert len(product_blocks) == 3 + + assert pageinfo == { + "hasPreviousPage": False, + "hasNextPage": False, + "startCursor": 0, + "endCursor": 2, + "totalItems": 3, + } + + expected = [ + { + "name": "SubBlockOneForTest", + "endDate": None, + "description": "Test Sub Block One", + "status": "ACTIVE", + "tag": "TEST", + "resourceTypes": [ + {"resourceType": "int_field", "description": ""}, + {"resourceType": "str_field", "description": ""}, + ], + "dependsOn": [], + "inUseBy": [{"name": "ProductBlockWithListUnionForTest", "description": "Test Union Sub Block"}], + }, + { + "name": "SubBlockTwoForTest", + "endDate": None, + "description": "Test Sub Block Two", + "status": "ACTIVE", + "tag": "TEST", + "resourceTypes": [{"resourceType": "int_field_2", "description": ""}], + "dependsOn": [], + "inUseBy": [{"name": "ProductBlockWithListUnionForTest", "description": "Test Union Sub Block"}], + }, + { + "name": "ProductBlockWithListUnionForTest", + "endDate": None, + "description": "Test Union Sub Block", + "status": "ACTIVE", + "tag": "TEST", + "resourceTypes": [ + {"resourceType": "int_field", "description": ""}, + {"resourceType": "str_field", "description": ""}, + {"resourceType": "list_field", "description": ""}, + ], + "dependsOn": [ + {"name": "SubBlockOneForTest", "description": "Test Sub Block One"}, + {"name": "SubBlockTwoForTest", "description": "Test Sub Block Two"}, + ], + "inUseBy": [], + }, + ] + + exclude_paths = exclude_paths = [f"root[{i}]['createdAt']" for i in range(len(expected))] + assert_no_diff(expected, product_blocks, exclude_paths=exclude_paths) + def test_product_blocks_has_previous_page(test_client): data = get_product_blocks_query(after=1) diff --git a/test/unit_tests/helpers.py b/test/unit_tests/helpers.py new file mode 100644 index 000000000..80acc0cfc --- /dev/null +++ b/test/unit_tests/helpers.py @@ -0,0 +1,10 @@ +import json + +from deepdiff import DeepDiff + + +def assert_no_diff(expected, actual, exclude_paths=None): + diff = DeepDiff(expected, actual, ignore_order=True, exclude_paths=exclude_paths) + prettydiff = f"Difference: {json.dumps(diff, indent=2, default=lambda x: str(x))}" + + assert diff == {}, f"Difference between expected and actual output\n{prettydiff}"