diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 15e1b3308..3d6b397a4 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -24,7 +24,7 @@ jobs: python-version: '3.9' cache: 'poetry' - name: Install black - run: poetry install --only=dev + run: poetry install - name: Run black run: poetry run black . --check @@ -39,7 +39,7 @@ jobs: python-version: '3.9' cache: 'poetry' - name: Install isort - run: poetry install --only=dev + run: poetry install - name: Check isort version run: poetry run isort --version - name: Run isort diff --git a/examples/tutorials/notebook/notebook_tutorial_2D_crm.ipynb b/examples/tutorials/notebook/notebook_tutorial_2D_crm.ipynb index 23c38cdc1..4da568c79 100644 --- a/examples/tutorials/notebook/notebook_tutorial_2D_crm.ipynb +++ b/examples/tutorials/notebook/notebook_tutorial_2D_crm.ipynb @@ -909,7 +909,12 @@ " operating_condition_params = fl.SimulationParams(\n", " # Create operating conditions using mach and reynolds number\n", " operating_condition=fl.operating_condition_from_mach_reynolds(\n", - " mach=0.2, reynolds=5e6, temperature=272.1, alpha=16 * u.deg, beta=0 * u.deg, project_length_unit=1 * u.m\n", + " mach=0.2,\n", + " reynolds=5e6,\n", + " temperature=272.1,\n", + " alpha=16 * u.deg,\n", + " beta=0 * u.deg,\n", + " project_length_unit=1 * u.m,\n", " ),\n", " )" ] @@ -1642,7 +1647,12 @@ " moment_center=[0.25, 0, 0], moment_length=[1, 1, 1], area=0.01\n", " ),\n", " operating_condition=fl.operating_condition_from_mach_reynolds(\n", - " mach=0.2, reynolds=5e6, temperature=272.1, alpha=16 * u.deg, beta=0 * u.deg, project_length_unit=1 * u.m\n", + " mach=0.2,\n", + " reynolds=5e6,\n", + " temperature=272.1,\n", + " alpha=16 * u.deg,\n", + " beta=0 * u.deg,\n", + " project_length_unit=1 * u.m,\n", " ),\n", " time_stepping=fl.Steady(\n", " max_steps=3000, CFL=fl.RampCFL(initial=20, final=300, ramp_steps=500)\n", diff --git a/flow360/cli/app.py b/flow360/cli/app.py index 3aa8865d7..389c89fdc 100644 --- a/flow360/cli/app.py +++ b/flow360/cli/app.py @@ -9,6 +9,7 @@ import toml from flow360.cli import dict_utils +from flow360.component.project_utils import show_projects_with_keyword_filter home = expanduser("~") # pylint: disable=invalid-name @@ -85,4 +86,16 @@ def configure(apikey, profile, dev, suppress_submit_warning, beta_features): click.echo("done.") +# For displaying all projects +@click.command("show_projects", context_settings={"show_default": True}) +@click.option("--keyword", "-k", help="Filter projects by keyword", default=None, type=str) +def show_projects(keyword): + """ + Display all available projects with optional keyword filter. + """ + + show_projects_with_keyword_filter(search_keyword=keyword) + + flow360.add_command(configure) +flow360.add_command(show_projects) diff --git a/flow360/component/project.py b/flow360/component/project.py index df22a0120..a7b1a2d8c 100644 --- a/flow360/component/project.py +++ b/flow360/component/project.py @@ -22,6 +22,7 @@ ProjectInterface, VolumeMeshInterfaceV2, ) +from flow360.component.project_utils import show_projects_with_keyword_filter from flow360.component.resource_base import Flow360Resource from flow360.component.simulation.entity_info import GeometryEntityInfo from flow360.component.simulation.outputs.output_entities import ( @@ -418,6 +419,18 @@ class Project(pd.BaseModel): _project_webapi: Optional[RestApi] = pd.PrivateAttr(None) _root_simulation_json: Optional[dict] = pd.PrivateAttr(None) + @classmethod + def show_remote(cls, search_keyword: Union[None, str] = None): + """ + Shows all projects on the cloud. + + Parameters + ---------- + search_keyword : str, optional + + """ + show_projects_with_keyword_filter(search_keyword) + @property def id(self) -> str: """ diff --git a/flow360/component/project_utils.py b/flow360/component/project_utils.py new file mode 100644 index 000000000..72065003a --- /dev/null +++ b/flow360/component/project_utils.py @@ -0,0 +1,119 @@ +""" +Support class and functions for project interface. +""" + +import datetime +from typing import List, Literal, Optional + +import pydantic as pd + +from flow360.cloud.rest_api import RestApi +from flow360.component.interfaces import ProjectInterface +from flow360.component.utils import parse_datetime +from flow360.log import log + + +class AssetStatistics(pd.BaseModel): + """Statistics for an asset""" + + count: int + successCount: int + runningCount: int + divergedCount: int + errorCount: int + + +class ProjectStatistics(pd.BaseModel): + """Statistics for a project""" + + geometry: Optional[AssetStatistics] = pd.Field(None, alias="Geometry") + surface_mesh: Optional[AssetStatistics] = pd.Field(None, alias="SurfaceMesh") + volume_mesh: Optional[AssetStatistics] = pd.Field(None, alias="VolumeMesh") + case: Optional[AssetStatistics] = pd.Field(None, alias="Case") + + +class ProjectInfo(pd.BaseModel): + """Information about a project, retrieved from the projects get API""" + + name: str + project_id: str = pd.Field(alias="id") + tags: list[str] = pd.Field() + description: str = pd.Field() + statistics: ProjectStatistics = pd.Field() + solver_version: Optional[str] = pd.Field( + None, alias="solverVersion", description="If None then the project is from old database" + ) + created_at: str = pd.Field(alias="createdAt") + root_item_type: Literal["Geometry", "SurfaceMesh", "VolumeMesh"] = pd.Field( + alias="rootItemType" + ) + + @pd.computed_field + @property + def local_time_zone_created_time(self) -> datetime.datetime: + """Convert string time to datetime obj and also convert to local time zone""" + return parse_datetime(self.created_at) + + +class ProjectRecords(pd.BaseModel): + """Holds all records of a user's project""" + + records: List[ProjectInfo] = pd.Field() + + def __str__(self): + """Print out all info about the project""" + if not self.records: + output_str = "No matching projects found. Try skip naming patterns to show all." + return output_str + output_str = ">>> Projects sorted by creation time:\n" + # pylint: disable=not-an-iterable + for item in self.records: + output_str += f" Name: {item.name}\n" + output_str += f" Created at: {item.local_time_zone_created_time.strftime('%Y-%m-%d %H:%M %Z')}\n" + output_str += f" Created with: {item.root_item_type}\n" + output_str += f" ID: {item.project_id}\n" + output_str += ( + f" Link: https://flow360.simulation.cloud/workbench/{item.project_id}\n" + ) + if item.tags: + output_str += f" Tags: {item.tags}\n" + if item.description: + output_str += f" Description: {item.description}\n" + if item.statistics.geometry: + output_str += f" Geometry count: {item.statistics.geometry.count}\n" + if item.statistics.surface_mesh: + output_str += f" Surface Mesh count: {item.statistics.surface_mesh.count}\n" + if item.statistics.volume_mesh: + output_str += f" Volume Mesh count: {item.statistics.volume_mesh.count}\n" + if item.statistics.case: + output_str += f" Case count: {item.statistics.case.count}\n" + + output_str += "\n" + return output_str + + +def show_projects_with_keyword_filter(search_keyword: str): + """Show all projects with a keyword filter""" + # pylint: disable=invalid-name + MAX_DISPLAYABLE_ITEM_COUNT = 200 + MAX_SEARCHABLE_ITEM_COUNT = 1000 + _api = RestApi(ProjectInterface.endpoint, id=None) + resp = _api.get( + params={ + "page": "0", + "size": MAX_SEARCHABLE_ITEM_COUNT, + "filterKeywords": search_keyword, + "sortFields": ["createdAt"], + "sortDirections": ["asc"], + } + ) + + all_projects = ProjectRecords.model_validate({"records": resp["records"]}) + log.info("%s", str(all_projects)) + + if resp["total"] > MAX_DISPLAYABLE_ITEM_COUNT: + log.warning( + f"Total number of projects matching the keyword on the cloud is {resp['total']}, " + f"but only the latest {MAX_DISPLAYABLE_ITEM_COUNT} will be displayed. " + ) + log.info("Total number of matching projects on the cloud: %d", resp["total"]) diff --git a/flow360/component/simulation/framework/entity_registry.py b/flow360/component/simulation/framework/entity_registry.py index ebe334180..471afa5aa 100644 --- a/flow360/component/simulation/framework/entity_registry.py +++ b/flow360/component/simulation/framework/entity_registry.py @@ -1,6 +1,5 @@ """Registry for managing and storing instances of various entity types.""" -import re from typing import Any, Dict, Union import pydantic as pd @@ -11,6 +10,7 @@ MergeConflictError, _merge_objects, ) +from flow360.component.utils import _naming_pattern_handler from flow360.log import log @@ -109,13 +109,7 @@ def find_by_naming_pattern( List[EntityBase]: A list of entities whose names match the pattern. """ matched_entities = [] - if "*" in pattern: - # Convert wildcard to regex pattern - regex_pattern = "^" + pattern.replace("*", ".*") + "$" - else: - regex_pattern = f"^{pattern}$" # Exact match if no '*' - - regex = re.compile(regex_pattern) + regex = _naming_pattern_handler(pattern=pattern) # pylint: disable=no-member for entity_list in self.internal_registry.values(): matched_entities.extend(filter(lambda x: regex.match(x.name), entity_list)) diff --git a/flow360/component/utils.py b/flow360/component/utils.py index eb2f4db3b..9365f50a7 100644 --- a/flow360/component/utils.py +++ b/flow360/component/utils.py @@ -709,3 +709,20 @@ def _local_download_file( shutil.copy(expected_local_file, new_local_file) return _local_download_file + + +def _naming_pattern_handler(pattern: str) -> re.Pattern[str]: + """ + Handler of the user supplied naming pattern. + This enables both glob pattern and regexp pattern. + If "*" is found in the pattern, it will be treated as a glob pattern. + """ + + if "*" in pattern: + # Convert wildcard to regex pattern + regex_pattern = "^" + pattern.replace("*", ".*") + "$" + else: + regex_pattern = f"^{pattern}$" # Exact match if no '*' + + regex = re.compile(regex_pattern) + return regex diff --git a/tests/data/mock_webapi/get_projects_resp.json b/tests/data/mock_webapi/get_projects_resp.json new file mode 100644 index 000000000..8894f1cee --- /dev/null +++ b/tests/data/mock_webapi/get_projects_resp.json @@ -0,0 +1,773 @@ +{ + "data": { + "page": 0, + "size": 15, + "total": 15, + "records": [ + { + "userId": "MOCKUSERID", + "id": "prj-60678a52-6efb-4995-8686-7fc5f37f7a16", + "name": "With SolverVersion", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-a74b375c-2767-411c-8c8c-3294333b0c31", + "latestItemType": "Geometry", + "rootItemId": "geo-a74b375c-2767-411c-8c8c-3294333b0c31", + "rootItemType": "Geometry", + "createdAt": "2025-01-14T02:43:11.130702Z", + "updatedAt": "2025-01-14T02:43:59.706Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-14T02:43:27.129Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-6ab2018e-3471-452e-aa86-be52f62d7dbd", + "name": "No SolverVersion", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-0eb42d56-3a0a-40f5-bf86-2d4a080eb15c", + "latestItemType": "Geometry", + "rootItemId": "geo-0eb42d56-3a0a-40f5-bf86-2d4a080eb15c", + "rootItemType": "Geometry", + "createdAt": "2025-01-14T02:41:55.986274Z", + "updatedAt": "2025-01-14T02:42:45.511Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-14T02:42:12.507Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-b5b247fa-267c-460f-b7a2-629ceead0a10", + "name": "temp", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "case-7a0920b6-00de-467b-b2a6-93706796361b", + "lastOpenItemType": "Case", + "latestItemId": "case-7a0920b6-00de-467b-b2a6-93706796361b", + "latestItemType": "Case", + "rootItemId": "geo-1db35101-197b-4d97-98f5-1d9ed356ae3c", + "rootItemType": "Geometry", + "createdAt": "2025-01-10T19:08:52.173108Z", + "updatedAt": "2025-01-10T19:55:19.668Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-10T19:09:08.957Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-afba2253-39da-43d8-ba72-3f8d80090fa3", + "name": "temp", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-d4c16198-6fcd-43a6-aeb5-43c0cf61aebd", + "latestItemType": "Geometry", + "rootItemId": "geo-d4c16198-6fcd-43a6-aeb5-43c0cf61aebd", + "rootItemType": "Geometry", + "createdAt": "2025-01-10T19:04:48.712434Z", + "updatedAt": "2025-01-10T19:06:11.91Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-10T19:05:05.411Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-cc2ce555-1fc0-41d3-9711-b43f3666b826", + "name": "temp", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-813723fc-4508-4b81-9642-7d4dac296ef8", + "latestItemType": "Geometry", + "rootItemId": "geo-813723fc-4508-4b81-9642-7d4dac296ef8", + "rootItemType": "Geometry", + "createdAt": "2025-01-10T17:26:08.548284Z", + "updatedAt": "2025-01-10T17:27:49.293Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-10T17:26:26.34Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-34c5754f-6faa-4a39-97d6-66498395a2ea", + "name": "DELETE ME!!!", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "TestGhostEntities-24.11.16", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-230bf032-84fc-40f1-9c60-84c2ac29cfce", + "latestItemType": "Geometry", + "rootItemId": "geo-230bf032-84fc-40f1-9c60-84c2ac29cfce", + "rootItemType": "Geometry", + "createdAt": "2025-01-09T21:15:47.128763Z", + "updatedAt": "2025-01-09T21:16:45.67Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-09T21:16:04.746Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-7c2eea75-f233-441e-bbef-c3fed06819e6", + "name": "Python Project (Geometry, from file)", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 0, + "runningCount": 0, + "divergedCount": 1, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "vm-d0da34c4-0f92-4316-9891-ce76b25ac374", + "lastOpenItemType": "VolumeMesh", + "latestItemId": "case-2b5266bd-bd5c-4ae5-9179-f7bc7c1fa40d", + "latestItemType": "Case", + "rootItemId": "geo-51fdb35e-5f48-4e55-a82a-ee370e661b0b", + "rootItemType": "Geometry", + "createdAt": "2025-01-08T21:52:22.658425Z", + "updatedAt": "2025-01-08T22:13:51.064Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-08T21:52:40.385Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-049cbb3b-aae4-4f76-811d-f27a50906ac9", + "name": "Python Project (Geometry, from file)", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "geo-1d7680a3-fba8-41b3-b4ca-1c154f015640", + "latestItemType": "Geometry", + "rootItemId": "geo-1d7680a3-fba8-41b3-b4ca-1c154f015640", + "rootItemType": "Geometry", + "createdAt": "2025-01-07T18:46:29.809161Z", + "updatedAt": "2025-01-07T19:25:05.988Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-07T18:46:47.359Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-cffda68c-fdbe-4391-a5a8-3f81bf3b8b1a", + "name": "Untitled", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": { + "fontColor": "#0062FF", + "bgColor": "#E6EFFF", + "icon": "fc:flight-takeoff_outline" + }, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "vm-b9f50302-fec9-4742-9fcc-eea7aded7072", + "lastOpenItemType": "VolumeMesh", + "latestItemId": "vm-b9f50302-fec9-4742-9fcc-eea7aded7072", + "latestItemType": "VolumeMesh", + "rootItemId": "geo-7d0e901b-21bb-4e92-b3c1-b6d8ca313ccf", + "rootItemType": "Geometry", + "createdAt": "2025-01-06T23:42:19.291358Z", + "updatedAt": "2025-01-07T17:34:49.612Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2025-01-06T23:42:22.592Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-cab7ad0b-fdb2-46e2-9da3-018101b4ccae", + "name": "test_VM_flow", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": { + "fontColor": "#0062FF", + "bgColor": "#E6EFFF", + "icon": "fc:flight-takeoff_outline" + }, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": null, + "lastOpenItemType": null, + "latestItemId": "vm-819e1261-4ebc-49e0-921e-fa9a3549350e", + "latestItemType": "VolumeMesh", + "rootItemId": "vm-819e1261-4ebc-49e0-921e-fa9a3549350e", + "rootItemType": "VolumeMesh", + "createdAt": "2024-12-13T04:15:34.742297Z", + "updatedAt": "2024-12-13T04:18:36.168Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "completed", + "rootItemUploadedAt": "2024-12-13T04:15:54.032Z", + "rootItemVisualizationStatus": "completed", + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-3ede1491-7c2d-477c-bf8d-3b4bfba12fab", + "name": "Python Project (Geometry, from file)", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "case-8d95ff2b-4a8e-4b8f-a517-840e1a724aee", + "lastOpenItemType": "Case", + "latestItemId": "vm-64196eb7-efd0-4282-ad82-6a641d58430d", + "latestItemType": "VolumeMesh", + "rootItemId": "geo-fc5fa69b-58ff-4aef-a20b-c8cb5066f79c", + "rootItemType": "Geometry", + "createdAt": "2024-12-12T20:59:40.189428Z", + "updatedAt": "2024-12-12T21:01:30.299Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2024-12-12T20:59:57.4Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-83fd8a01-de82-4fe7-a442-3a0eb0652a64", + "name": "Python Project from Geometry", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "vm-daf60f77-3830-422a-895f-4dd9573b7487", + "lastOpenItemType": "VolumeMesh", + "latestItemId": "vm-daf60f77-3830-422a-895f-4dd9573b7487", + "latestItemType": "VolumeMesh", + "rootItemId": "geo-30c11cc7-161c-4380-a9d3-ed7de0f5218d", + "rootItemType": "Geometry", + "createdAt": "2024-12-12T16:14:24.94298Z", + "updatedAt": "2024-12-12T20:06:01.416Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2024-12-12T16:14:40.697Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-97f6971d-a0dc-49fc-b10e-9f1acfa02bef", + "name": "CAERI-veryLight-Flynn360-tmp", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 0, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 1 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "case-aab0abfe-b9eb-4566-ae49-67725b01d8b5", + "lastOpenItemType": "Case", + "latestItemId": "case-aab0abfe-b9eb-4566-ae49-67725b01d8b5", + "latestItemType": "Case", + "rootItemId": "vm-7929b400-7cfb-4222-a1b2-528b48f883ea", + "rootItemType": "VolumeMesh", + "createdAt": "2024-12-11T22:50:53.016237Z", + "updatedAt": "2024-12-11T23:48:01.401Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "completed", + "rootItemUploadedAt": "2024-12-11T22:51:09.59Z", + "rootItemVisualizationStatus": "completed", + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-f52dbf49-c226-47cc-b406-94113ff19e88", + "name": "Python Project (Geometry, from file)", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "SurfaceMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Case": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + }, + "Geometry": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "case-41c029cb-4147-496a-80c6-43ac52759671", + "lastOpenItemType": "Case", + "latestItemId": "case-41c029cb-4147-496a-80c6-43ac52759671", + "latestItemType": "Case", + "rootItemId": "geo-9b99633c-3783-4975-9487-13b687007d70", + "rootItemType": "Geometry", + "createdAt": "2024-12-06T15:44:07.611631Z", + "updatedAt": "2025-01-08T15:13:47.736Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "processed", + "rootItemUploadedAt": "2024-12-06T15:44:25.999Z", + "rootItemVisualizationStatus": null, + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + }, + { + "userId": "MOCKUSERID", + "id": "prj-7b295c76-8441-4b55-9443-337f941d2691", + "name": "MeshProviderProject", + "tags": [], + "parentFolderId": "ROOT.FLOW360", + "deleted": false, + "description": "", + "icon": null, + "statistics": { + "VolumeMesh": { + "count": 1, + "successCount": 1, + "runningCount": 0, + "divergedCount": 0, + "errorCount": 0 + } + }, + "solverVersion": "release-24.11", + "parentFolders": null, + "viewed": true, + "lastOpenDraftId": null, + "lastOpenItemId": "vm-e7c56561-6fc1-42a1-ae53-8f1456676a46", + "lastOpenItemType": "VolumeMesh", + "latestItemId": "vm-e7c56561-6fc1-42a1-ae53-8f1456676a46", + "latestItemType": "VolumeMesh", + "rootItemId": "vm-e7c56561-6fc1-42a1-ae53-8f1456676a46", + "rootItemType": "VolumeMesh", + "createdAt": "2024-12-04T18:43:53.646164Z", + "updatedAt": "2025-01-10T14:25:29.057Z", + "createdBy": "MOCKUSERID", + "updatedBy": "MOCKUSERID", + "canOpen": true, + "rootItemStatus": "completed", + "rootItemUploadedAt": "2024-12-04T18:44:09.688Z", + "rootItemVisualizationStatus": "completed", + "rootItemUploadStatus": "success", + "copyStatus": "notCopy", + "copyFromResourceCount": 0, + "copiedResourceCount": 0, + "rootItemDisplayStatus": "completed" + } + ] + } +} \ No newline at end of file diff --git a/tests/mock_server.py b/tests/mock_server.py index 2de4e3110..9c9a624dd 100644 --- a/tests/mock_server.py +++ b/tests/mock_server.py @@ -259,6 +259,16 @@ def json(): return res +class MockResponseAllProjects(MockResponse): + """response of projects get""" + + @staticmethod + def json(): + with open(os.path.join(here, "data/mock_webapi/get_projects_resp.json")) as fh: + res = json.load(fh) + return res + + class MockResponseProjectTree(MockResponse): """response for Project.from_cloud(id="prj-41d2333b-85fd-4bed-ae13-15dcb6da519e")'s tree json""" @@ -475,6 +485,7 @@ def json(self): "/v2/cases/case-69b8c249-fce5-412a-9927-6a79049deebb/simulation/file": MockResponseProjectCaseSimConfig, "/cases/case-84d4604e-f3cd-4c6b-8517-92a80a3346d3": MockResponseProjectCaseFork, "/v2/cases/case-84d4604e-f3cd-4c6b-8517-92a80a3346d3/simulation/file": MockResponseProjectCaseForkSimConfig, + "/v2/projects": MockResponseAllProjects, } PUT_RESPONSE_MAP = { @@ -494,7 +505,7 @@ def json(self): def mock_webapi(type, url, params): method = url.split("flow360")[-1] - print("<><><><><><<><<><<><><>><<>") + print("<><><><><><><><><><><><><><><>") print(type, method) diff --git a/tests/simulation/asset/ref/ref_all_projects.json b/tests/simulation/asset/ref/ref_all_projects.json new file mode 100644 index 000000000..e7df2ca11 --- /dev/null +++ b/tests/simulation/asset/ref/ref_all_projects.json @@ -0,0 +1 @@ +{"records":[{"name":"With SolverVersion","id":"prj-60678a52-6efb-4995-8686-7fc5f37f7a16","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-14T02:43:11.130702Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-14T02:43:11.130702Z"},{"name":"No SolverVersion","id":"prj-6ab2018e-3471-452e-aa86-be52f62d7dbd","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-14T02:41:55.986274Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-14T02:41:55.986274Z"},{"name":"temp","id":"prj-b5b247fa-267c-460f-b7a2-629ceead0a10","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0}},"solverVersion":"release-24.11","createdAt":"2025-01-10T19:08:52.173108Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-10T19:08:52.173108Z"},{"name":"temp","id":"prj-afba2253-39da-43d8-ba72-3f8d80090fa3","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-10T19:04:48.712434Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-10T19:04:48.712434Z"},{"name":"temp","id":"prj-cc2ce555-1fc0-41d3-9711-b43f3666b826","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-10T17:26:08.548284Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-10T17:26:08.548284Z"},{"name":"DELETE ME!!!","id":"prj-34c5754f-6faa-4a39-97d6-66498395a2ea","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"TestGhostEntities-24.11.16","createdAt":"2025-01-09T21:15:47.128763Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-09T21:15:47.128763Z"},{"name":"Python Project (Geometry, from file)","id":"prj-7c2eea75-f233-441e-bbef-c3fed06819e6","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":0,"runningCount":0,"divergedCount":1,"errorCount":0}},"solverVersion":"release-24.11","createdAt":"2025-01-08T21:52:22.658425Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-08T21:52:22.658425Z"},{"name":"Python Project (Geometry, from file)","id":"prj-049cbb3b-aae4-4f76-811d-f27a50906ac9","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":null,"VolumeMesh":null,"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-07T18:46:29.809161Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-07T18:46:29.809161Z"},{"name":"Untitled","id":"prj-cffda68c-fdbe-4391-a5a8-3f81bf3b8b1a","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":null},"solverVersion":"release-24.11","createdAt":"2025-01-06T23:42:19.291358Z","rootItemType":"Geometry","local_time_zone_created_time":"2025-01-06T23:42:19.291358Z"},{"name":"test_VM_flow","id":"prj-cab7ad0b-fdb2-46e2-9da3-018101b4ccae","tags":[],"description":"","statistics":{"Geometry":null,"SurfaceMesh":null,"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":null},"solverVersion":"release-24.11","createdAt":"2024-12-13T04:15:34.742297Z","rootItemType":"VolumeMesh","local_time_zone_created_time":"2024-12-13T04:15:34.742297Z"},{"name":"Python Project (Geometry, from file)","id":"prj-3ede1491-7c2d-477c-bf8d-3b4bfba12fab","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0}},"solverVersion":"release-24.11","createdAt":"2024-12-12T20:59:40.189428Z","rootItemType":"Geometry","local_time_zone_created_time":"2024-12-12T20:59:40.189428Z"},{"name":"Python Project from Geometry","id":"prj-83fd8a01-de82-4fe7-a442-3a0eb0652a64","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0}},"solverVersion":"release-24.11","createdAt":"2024-12-12T16:14:24.94298Z","rootItemType":"Geometry","local_time_zone_created_time":"2024-12-12T16:14:24.942980Z"},{"name":"CAERI-veryLight-Flynn360-tmp","id":"prj-97f6971d-a0dc-49fc-b10e-9f1acfa02bef","tags":[],"description":"","statistics":{"Geometry":null,"SurfaceMesh":null,"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":0,"runningCount":0,"divergedCount":0,"errorCount":1}},"solverVersion":"release-24.11","createdAt":"2024-12-11T22:50:53.016237Z","rootItemType":"VolumeMesh","local_time_zone_created_time":"2024-12-11T22:50:53.016237Z"},{"name":"Python Project (Geometry, from file)","id":"prj-f52dbf49-c226-47cc-b406-94113ff19e88","tags":[],"description":"","statistics":{"Geometry":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"SurfaceMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0}},"solverVersion":"release-24.11","createdAt":"2024-12-06T15:44:07.611631Z","rootItemType":"Geometry","local_time_zone_created_time":"2024-12-06T15:44:07.611631Z"},{"name":"MeshProviderProject","id":"prj-7b295c76-8441-4b55-9443-337f941d2691","tags":[],"description":"","statistics":{"Geometry":null,"SurfaceMesh":null,"VolumeMesh":{"count":1,"successCount":1,"runningCount":0,"divergedCount":0,"errorCount":0},"Case":null},"solverVersion":"release-24.11","createdAt":"2024-12-04T18:43:53.646164Z","rootItemType":"VolumeMesh","local_time_zone_created_time":"2024-12-04T18:43:53.646164Z"}]} \ No newline at end of file diff --git a/tests/simulation/asset/test_display_all_projects.py b/tests/simulation/asset/test_display_all_projects.py new file mode 100644 index 000000000..8e71eb211 --- /dev/null +++ b/tests/simulation/asset/test_display_all_projects.py @@ -0,0 +1,21 @@ +import json + +import pytest + +from flow360.cloud.rest_api import RestApi +from flow360.component.interfaces import ProjectInterface +from flow360.component.project_utils import ProjectRecords + + +@pytest.fixture(autouse=True) +def change_test_dir(request, monkeypatch): + monkeypatch.chdir(request.fspath.dirname) + + +def test_showing_remote_filtered_projects(mock_id, mock_response): + _api = RestApi(ProjectInterface.endpoint, id=None) + resp = _api.get() + all_projects = ProjectRecords.model_validate({"records": resp["records"]}) + with open("ref/ref_all_projects.json", "r") as f: + ref_all_projects = ProjectRecords.model_validate(json.load(f)) + assert all_projects == ref_all_projects diff --git a/tests/simulation/service/test_integration_geometry_metadata.py b/tests/simulation/service/test_integration_geometry_metadata.py index ce1e7db06..d40da784e 100644 --- a/tests/simulation/service/test_integration_geometry_metadata.py +++ b/tests/simulation/service/test_integration_geometry_metadata.py @@ -1,5 +1,4 @@ import os -import shutil import pytest