From 27ceb6f8ca1d3e8d6b6e4255c62590752222a341 Mon Sep 17 00:00:00 2001 From: Matus Drobuliak <60785969+matusdrobuliak66@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:34:03 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20PUT=20project?= =?UTF-8?q?=20endpoint=20(#6604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/specs/web-server/_projects_crud.py | 9 - .../api_schemas_webserver/projects.py | 17 +- .../api/v0/openapi.yaml | 100 +--------- .../projects/_crud_handlers.py | 175 +----------------- .../02/test_projects_crud_handlers.py | 157 ---------------- .../02/test_projects_states_handlers.py | 18 +- .../unit/with_dbs/03/folders/test_folders.py | 2 +- .../test_meta_modeling_iterations.py | 27 +-- .../with_dbs/03/version_control/conftest.py | 13 +- ...t_workspaces__folders_and_projects_crud.py | 2 +- 10 files changed, 51 insertions(+), 469 deletions(-) diff --git a/api/specs/web-server/_projects_crud.py b/api/specs/web-server/_projects_crud.py index 46073f921fc..640abe4f2b6 100644 --- a/api/specs/web-server/_projects_crud.py +++ b/api/specs/web-server/_projects_crud.py @@ -22,7 +22,6 @@ ProjectGet, ProjectListItem, ProjectPatch, - ProjectReplace, ) from models_library.generics import Envelope from models_library.projects import ProjectID @@ -104,14 +103,6 @@ async def get_project(project_id: ProjectID): ... -@router.put( - "/projects/{project_id}", - response_model=Envelope[ProjectGet], -) -async def replace_project(project_id: ProjectID, _replace: ProjectReplace): - """Replaces (i.e. full update) a project resource""" - - @router.patch( "/projects/{project_id}", response_model=None, diff --git a/packages/models-library/src/models_library/api_schemas_webserver/projects.py b/packages/models-library/src/models_library/api_schemas_webserver/projects.py index 2d8cd69ab93..601ac1e6d15 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/projects.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/projects.py @@ -121,18 +121,6 @@ class ProjectReplace(InputSchema): ) -class ProjectUpdate(InputSchema): - name: ShortTruncatedStr = FieldNotRequired() - description: LongTruncatedStr = FieldNotRequired() - thumbnail: HttpUrlWithCustomMinLength = FieldNotRequired() - workbench: NodesDict = FieldNotRequired() - access_rights: dict[GroupIDStr, AccessRights] = FieldNotRequired() - tags: list[int] = FieldNotRequired() - classifiers: list[ClassifierID] = FieldNotRequired() - ui: StudyUI | None = None - quality: dict[str, Any] = FieldNotRequired() - - class ProjectPatch(InputSchema): name: ShortTruncatedStr = FieldNotRequired() description: LongTruncatedStr = FieldNotRequired() @@ -143,6 +131,10 @@ class ProjectPatch(InputSchema): ui: StudyUI | None = FieldNotRequired() quality: dict[str, Any] = FieldNotRequired() + _empty_is_none = validator("thumbnail", allow_reuse=True, pre=True)( + empty_str_to_none_pre_validator + ) + __all__: tuple[str, ...] = ( "EmptyModel", @@ -151,6 +143,5 @@ class ProjectPatch(InputSchema): "ProjectGet", "ProjectListItem", "ProjectReplace", - "ProjectUpdate", "TaskProjectGet", ) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 298e13fe373..fe4508f89f2 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -3221,33 +3221,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_ProjectGet_' - put: - tags: - - projects - summary: Replace Project - description: Replaces (i.e. full update) a project resource - operationId: replace_project - parameters: - - required: true - schema: - title: Project Id - type: string - format: uuid - name: project_id - in: path - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/ProjectReplace' - required: true - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Envelope_ProjectGet_' delete: tags: - projects @@ -4331,7 +4304,7 @@ paths: '403': description: ProjectInvalidRightsError '404': - description: ProjectNotFoundError, UserDefaultWalletNotFoundError + description: UserDefaultWalletNotFoundError, ProjectNotFoundError '409': description: ProjectTooManyProjectOpenedError '422': @@ -10603,15 +10576,15 @@ components: title: Value type: boolean description: True if the project is locked + status: + allOf: + - $ref: '#/components/schemas/ProjectStatus' + description: The status of the project owner: title: Owner allOf: - $ref: '#/components/schemas/Owner' description: If locked, the user that owns the lock - status: - allOf: - - $ref: '#/components/schemas/ProjectStatus' - description: The status of the project additionalProperties: false ProjectMetadataGet: title: ProjectMetadataGet @@ -10741,68 +10714,6 @@ components: is_public: title: Is Public type: boolean - ProjectReplace: - title: ProjectReplace - required: - - uuid - - name - - description - - creationDate - - lastChangeDate - - workbench - - accessRights - type: object - properties: - uuid: - title: Uuid - type: string - format: uuid - name: - title: Name - type: string - description: - title: Description - type: string - thumbnail: - title: Thumbnail - maxLength: 2083 - minLength: 0 - type: string - format: uri - creationDate: - title: Creationdate - pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z - type: string - lastChangeDate: - title: Lastchangedate - pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z - type: string - workbench: - title: Workbench - type: object - additionalProperties: - $ref: '#/components/schemas/Node' - accessRights: - title: Accessrights - type: object - additionalProperties: - $ref: '#/components/schemas/models_library__projects_access__AccessRights' - tags: - title: Tags - type: array - items: - type: integer - default: [] - classifiers: - title: Classifiers - type: array - items: - type: string - ui: - $ref: '#/components/schemas/StudyUI' - quality: - title: Quality - type: object ProjectRunningState: title: ProjectRunningState required: @@ -10841,6 +10752,7 @@ components: - EXPORTING - OPENING - OPENED + - MAINTAINING type: string description: An enumeration. ProjectTypeAPI: diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py index 11d1f701b32..9e5a7667b7d 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py @@ -5,11 +5,9 @@ """ import functools -import json import logging from aiohttp import web -from jsonschema import ValidationError as JsonSchemaValidationError from models_library.api_schemas_webserver.projects import ( EmptyModel, ProjectCopyOverride, @@ -18,7 +16,6 @@ ProjectPatch, ) from models_library.generics import Envelope -from models_library.projects import Project from models_library.projects_state import ProjectLocked from models_library.rest_ordering import OrderBy from models_library.rest_pagination import Page @@ -44,7 +41,6 @@ from .._meta import API_VTAG as VTAG from ..catalog.client import get_services_for_user_in_product -from ..director_v2 import api from ..folders.errors import FolderAccessForbiddenError, FolderNotFoundError from ..login.decorators import login_required from ..resource_manager.user_sessions import PROJECT_ID_KEY, managed_resource @@ -53,7 +49,6 @@ from ..users.api import get_user_fullname from ..workspaces.errors import WorkspaceAccessForbiddenError, WorkspaceNotFoundError from . import _crud_api_create, _crud_api_read, projects_api -from ._access_rights_api import check_user_project_permission from ._common_models import ProjectPathParams, RequestContext from ._crud_handlers_models import ( ProjectActiveParams, @@ -63,23 +58,16 @@ ProjectListWithJsonStrParams, ) from ._permalink_api import update_or_pop_permalink_in_project -from .db import ProjectDBAPI from .exceptions import ( ProjectDeleteError, ProjectInvalidRightsError, - ProjectInvalidUsageError, ProjectNotFoundError, ProjectOwnerNotFoundInTheProjectAccessRightsError, WrongTagIdsInQueryError, ) from .lock import get_project_locked_state from .models import ProjectDict -from .nodes_utils import update_frontend_outputs -from .utils import ( - any_node_inputs_changed, - get_project_unavailable_services, - project_uses_available_services, -) +from .utils import get_project_unavailable_services, project_uses_available_services # When the user requests a project with a repo, the working copy might differ from # the repo project. A middleware in the meta module (if active) will resolve @@ -113,7 +101,7 @@ async def _wrapper(request: web.Request) -> web.StreamResponse: FolderAccessForbiddenError, WorkspaceAccessForbiddenError, ) as exc: - raise web.HTTPUnauthorized(reason=f"{exc}") from exc + raise web.HTTPForbidden(reason=f"{exc}") from exc return _wrapper @@ -400,165 +388,6 @@ async def get_project_inactivity(request: web.Request): # -@routes.put(f"/{VTAG}/projects/{{project_id}}", name="replace_project") -@login_required -@permission_required("project.update") -@permission_required("services.pipeline.*") # due to update_pipeline_db -async def replace_project(request: web.Request): - """ - In a PUT request, the enclosed entity is considered to be a modified version of - the resource stored on the origin server, and the client is requesting that the - stored version be replaced. - - With PATCH, however, the enclosed entity contains a set of instructions describing how a - resource currently residing on the origin server should be modified to produce a new version. - - Also, another difference is that when you want to update a resource with PUT request, you have to send - the full payload as the request whereas with PATCH, you only send the parameters which you want to update. - - Raises: - web.HTTPUnprocessableEntity: (422) if validation of request parameters fail - web.HTTPBadRequest: invalid body encoding - web.HTTPConflict: Cannot replace while pipeline is running - web.HTTPBadRequest: jsonschema validatio error - web.HTTPForbidden: Not enough access rights to replace this project - web.HTTPNotFound: This project was not found - """ - - db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(request.app) - req_ctx = RequestContext.parse_obj(request) - path_params = parse_request_path_parameters_as(ProjectPathParams, request) - - try: - new_project = await request.json() - # NOTE: this is a temporary fix until proper Model is introduced in ProjectReplace - # Prune state field (just in case) - new_project.pop("state", None) - new_project.pop("permalink", None) - - except json.JSONDecodeError as exc: - raise web.HTTPBadRequest(reason="Invalid request body") from exc - - await check_user_permission( - request, - "project.update | project.workbench.node.inputs.update", - context={ - "dbapi": db, - "app": request.app, - "project_id": f"{path_params.project_id}", - "user_id": req_ctx.user_id, - "new_data": new_project, - }, - ) - - try: - Project.parse_obj(new_project) # validate - - current_project = await projects_api.get_project_for_user( - request.app, - project_uuid=f"{path_params.project_id}", - user_id=req_ctx.user_id, - include_state=True, - ) - - if current_project["accessRights"] != new_project["accessRights"]: - await check_user_permission(request, "project.access_rights.update") - - if await api.is_pipeline_running( - request.app, req_ctx.user_id, path_params.project_id - ) and any_node_inputs_changed(new_project, current_project): - # NOTE: This is a conservative measure that we take - # until nodeports logic is re-designed to tackle with this - # particular state. - # - # This measure avoid having a state with different node *links* in the - # comp-tasks table and the project's workbench column. - # The limitation is that nodeports only "sees" those in the comptask - # and this table does not add the new ones since it remains "blocked" - # for modification from that project while the pipeline runs. Therefore - # any extra link created while the pipeline is running can not - # be managed by nodeports because it basically "cannot see it" - # - # Responds https://httpstatuses.com/409: - # The request could not be completed due to a conflict with the current - # state of the target resource (i.e. pipeline is running). This code is used in - # situations where the user might be able to resolve the conflict - # and resubmit the request (front-end will show a pop-up with message below) - # - raise web.HTTPConflict( - reason=f"Project {path_params.project_id} cannot be modified while pipeline is still running." - ) - - user_project_permission = await check_user_project_permission( - request.app, - project_id=path_params.project_id, - user_id=req_ctx.user_id, - product_name=req_ctx.product_name, - permission="write", - ) - - new_project = await db.replace_project( - new_project, - req_ctx.user_id, - project_uuid=f"{path_params.project_id}", - product_name=req_ctx.product_name, - ) - - await update_frontend_outputs( - app=request.app, - user_id=req_ctx.user_id, - project_uuid=path_params.project_id, - old_project=current_project, - new_project=new_project, - ) - - await api.update_dynamic_service_networks_in_project( - request.app, path_params.project_id - ) - await api.create_or_update_pipeline( - request.app, - req_ctx.user_id, - path_params.project_id, - product_name=req_ctx.product_name, - ) - # Appends state - data = await projects_api.add_project_states_for_user( - user_id=req_ctx.user_id, - project=new_project, - is_template=False, - app=request.app, - ) - # Appends folder ID - user_specific_project_data_db = await db.get_user_specific_project_data_db( - project_uuid=path_params.project_id, - private_workspace_user_id_or_none=( - req_ctx.user_id - if user_project_permission.workspace_id is None - else None - ), - ) - data["folderId"] = user_specific_project_data_db.folder_id - - return web.json_response({"data": data}, dumps=json_dumps) - - except JsonSchemaValidationError as exc: - raise web.HTTPBadRequest( - reason=f"Invalid project update: {exc.message}" - ) from exc - - except ProjectInvalidRightsError as exc: - raise web.HTTPForbidden( - reason="You do not have sufficient rights to replace the project" - ) from exc - except ProjectInvalidUsageError as exc: - raise web.HTTPConflict( - reason="You may not add or remove nodes in the workbench using this entrypoint. TIP: use dedicated add/remove node entrypoints" - ) from exc - - except ProjectNotFoundError as exc: - raise web.HTTPNotFound from exc - - @routes.patch(f"/{VTAG}/projects/{{project_id}}", name="patch_project") @login_required @permission_required("project.update") diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py index ec856a80f05..bf69984d6af 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py @@ -4,11 +4,9 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -import random import re import uuid as uuidlib from collections.abc import Awaitable, Callable, Iterator -from copy import deepcopy from http import HTTPStatus from math import ceil from typing import Any @@ -19,10 +17,7 @@ from aioresponses import aioresponses from faker import Faker from models_library.products import ProductName -from models_library.projects_nodes import Node from models_library.projects_state import ProjectState -from models_library.services import ServiceKey -from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import parse_obj_as from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.webserver_login import UserInfoDict @@ -181,23 +176,6 @@ async def _assert_get_same_project( assert folder_id is None -async def _replace_project( - client: TestClient, project_update: dict, expected: HTTPStatus -) -> dict: - assert client.app - - # PUT /v0/projects/{project_id} - url = client.app.router["replace_project"].url_for( - project_id=project_update["uuid"] - ) - assert str(url) == f"{API_PREFIX}/projects/{project_update['uuid']}" - resp = await client.put(f"{url}", json=project_update) - data, error = await assert_status(resp, expected) - if not error: - assert_replaced(current_project=data, update_data=project_update) - return data - - @pytest.mark.parametrize( "user_role,expected", [ @@ -656,141 +634,6 @@ async def test_new_template_from_project( parse_obj_as(uuidlib.UUID, node_name) -# PUT -------- -@pytest.mark.parametrize( - "user_role,expected,expected_change_access", - [ - ( - UserRole.ANONYMOUS, - status.HTTP_401_UNAUTHORIZED, - status.HTTP_401_UNAUTHORIZED, - ), - (UserRole.GUEST, status.HTTP_200_OK, status.HTTP_403_FORBIDDEN), - (UserRole.USER, status.HTTP_200_OK, status.HTTP_200_OK), - (UserRole.TESTER, status.HTTP_200_OK, status.HTTP_200_OK), - ], -) -async def test_replace_project( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - expected, - expected_change_access, - all_group, - ensure_run_in_sequence_context_is_empty, -): - project_update = deepcopy(user_project) - project_update["description"] = "some updated from original project!!!" - await _replace_project(client, project_update, expected) - - # replacing the owner access is not possible, it will keep the owner as well - project_update["accessRights"].update( - {str(all_group["gid"]): {"read": True, "write": True, "delete": True}} - ) - await _replace_project(client, project_update, expected_change_access) - - -@pytest.mark.parametrize( - "user_role,expected", - [ - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (UserRole.GUEST, status.HTTP_200_OK), - (UserRole.USER, status.HTTP_200_OK), - (UserRole.TESTER, status.HTTP_200_OK), - ], -) -async def test_replace_project_updated_inputs( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - expected, - ensure_run_in_sequence_context_is_empty, -): - project_update = deepcopy(user_project) - # - # "inputAccess": { - # "Na": "ReadAndWrite", <-------- - # "Kr": "ReadOnly", - # "BCL": "ReadAndWrite", - # "NBeats": "ReadOnly", - # "Ligand": "Invisible", - # "cAMKII": "Invisible" - # }, - project_update["workbench"]["5739e377-17f7-4f09-a6ad-62659fb7fdec"]["inputs"][ - "Na" - ] = 55 - await _replace_project(client, project_update, expected) - - -@pytest.mark.parametrize( - "user_role,expected", - [ - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (UserRole.GUEST, status.HTTP_200_OK), - (UserRole.USER, status.HTTP_200_OK), - (UserRole.TESTER, status.HTTP_200_OK), - ], -) -async def test_replace_project_updated_readonly_inputs( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - expected, - ensure_run_in_sequence_context_is_empty, -): - project_update = deepcopy(user_project) - project_update["workbench"]["5739e377-17f7-4f09-a6ad-62659fb7fdec"]["inputs"][ - "Na" - ] = 55 - project_update["workbench"]["5739e377-17f7-4f09-a6ad-62659fb7fdec"]["inputs"][ - "Kr" - ] = 5 - await _replace_project(client, project_update, expected) - - -@pytest.fixture -def random_minimal_node(faker: Faker) -> Callable[[], Node]: - def _creator() -> Node: - return Node( - key=ServiceKey(f"simcore/services/comp/{faker.pystr().lower()}"), - version=faker.numerify("#.#.#"), - label=faker.pystr(), - ) - - return _creator - - -@pytest.mark.parametrize( - "user_role,expected", - [ - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (UserRole.GUEST, status.HTTP_409_CONFLICT), - (UserRole.USER, status.HTTP_409_CONFLICT), - (UserRole.TESTER, status.HTTP_409_CONFLICT), - ], -) -async def test_replace_project_adding_or_removing_nodes_raises_conflict( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - expected, - ensure_run_in_sequence_context_is_empty, - faker: Faker, - random_minimal_node: Callable[[], Node], -): - # try adding a node should not work - project_update = deepcopy(user_project) - new_node = random_minimal_node() - project_update["workbench"][faker.uuid4()] = jsonable_encoder(new_node) - await _replace_project(client, project_update, expected) - # try removing a node should not work - project_update = deepcopy(user_project) - project_update["workbench"].pop( - random.choice(list(project_update["workbench"].keys())) # noqa: S311 - ) - await _replace_project(client, project_update, expected) - - @pytest.fixture def mock_director_v2_inactivity( aioresponses_mocker: aioresponses, is_inactive: bool diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py index d81dbbdb352..02285ebb0d5 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py @@ -112,15 +112,18 @@ async def _replace_project( ) -> ProjectDict: assert client.app - # PUT /v0/projects/{project_id} - url = client.app.router["replace_project"].url_for( - project_id=project_update["uuid"] - ) + # PATCH /v0/projects/{project_id} + url = client.app.router["patch_project"].url_for(project_id=project_update["uuid"]) assert str(url) == f"{API_PREFIX}/projects/{project_update['uuid']}" - resp = await client.put(f"{url}", json=project_update) + resp = await client.patch(f"{url}", json=project_update) data, error = await assert_status(resp, expected) if not error: - assert_replaced(current_project=data, update_data=project_update) + url = client.app.router["get_project"].url_for( + project_id=project_update["uuid"] + ) + resp = await client.get(f"{url}") + get_data, _ = await assert_status(resp, HTTPStatus.OK) + assert_replaced(current_project=get_data, update_data=project_update) return data @@ -307,10 +310,11 @@ async def test_share_project( # user 2 can update the project if user 2 has write access project_update = deepcopy(new_project) project_update["name"] = "my super name" + project_update.pop("accessRights") await _replace_project( client, project_update, - expected.ok if share_rights["write"] else expected.forbidden, + expected.no_content if share_rights["write"] else expected.forbidden, ) # user 2 can delete projects if user 2 has delete access diff --git a/services/web/server/tests/unit/with_dbs/03/folders/test_folders.py b/services/web/server/tests/unit/with_dbs/03/folders/test_folders.py index 7643afa7367..345e3875628 100644 --- a/services/web/server/tests/unit/with_dbs/03/folders/test_folders.py +++ b/services/web/server/tests/unit/with_dbs/03/folders/test_folders.py @@ -313,7 +313,7 @@ async def test_project_listing_inside_of_private_folder( resp = await client.get(url) _, errors = await assert_status( resp, - status.HTTP_401_UNAUTHORIZED, + status.HTTP_403_FORBIDDEN, ) assert errors diff --git a/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_iterations.py b/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_iterations.py index 20cb885bdfa..98d688e1f20 100644 --- a/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_iterations.py +++ b/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_iterations.py @@ -11,7 +11,7 @@ from models_library.projects import Project from models_library.projects_nodes import Node from models_library.services_resources import ServiceResourcesDict -from models_library.utils.json_serialization import json_dumps +from models_library.utils.json_serialization import json_dumps, json_loads from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict @@ -34,6 +34,7 @@ meta_project_policy, projects_redirection_middleware, ) +from simcore_service_webserver.projects.db import ProjectDBAPI from simcore_service_webserver.projects.models import ProjectDict REQUEST_MODEL_POLICY = { @@ -144,13 +145,14 @@ async def test_iterators_workflow( project_data.update({key: modifications[key] for key in ("workbench", "ui")}) project_data["ui"].setdefault("currentNodeId", project_uuid) - response = await client.put( - f"/v0/projects/{project_data['uuid']}", - json=project_data, + db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(client.app) + project_data.pop("state") + await db.replace_project( + project_data, + logged_user["id"], + project_uuid=project_uuid, + product_name="osparc", ) - assert ( - response.status == REPLACE_PROJECT_ON_MODIFIED.status_code - ), await response.text() # TODO: create iterations, so user could explore parametrizations? @@ -260,11 +262,14 @@ async def _mock_catalog_get(*args, **kwarg): assert node.inputs node.inputs["linspace_stop"] = 4 - response = await client.put( - f"/v0/projects/{project_uuid}", - data=json_dumps(new_project.dict(**REQUEST_MODEL_POLICY)), + _new_project_data = new_project.dict(**REQUEST_MODEL_POLICY) + _new_project_data.pop("state") + await db.replace_project( + json_loads(json_dumps(_new_project_data)), + logged_user["id"], + project_uuid=project_uuid, + product_name="osparc", ) - assert response.status == status.HTTP_200_OK, await response.text() # RUN again them --------------------------------------------------------------------------- response = await client.post( diff --git a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py index 7343176760e..f412a99d56c 100644 --- a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py @@ -32,6 +32,7 @@ from simcore_service_webserver.db.models import UserRole from simcore_service_webserver.db.plugin import APP_AIOPG_ENGINE_KEY from simcore_service_webserver.log import setup_logging +from simcore_service_webserver.projects.db import ProjectDBAPI from simcore_service_webserver.projects.models import ProjectDict from tenacity.asyncio import AsyncRetrying from tenacity.stop import stop_after_delay @@ -229,9 +230,15 @@ async def _go(client: TestClient, project_uuid: UUID) -> None: ) assert response.status == status.HTTP_201_CREATED project["workbench"] = {node_id: jsonable_encoder(node)} - resp = await client.put(f"{VX}/projects/{project_uuid}", json=project) - body = await resp.json() - assert resp.status == 200, str(body) + + db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(client.app) + project.pop("state") + await db.replace_project( + project, + logged_user["id"], + project_uuid=project["uuid"], + product_name="osparc", + ) return _go diff --git a/services/web/server/tests/unit/with_dbs/03/workspaces/test_workspaces__folders_and_projects_crud.py b/services/web/server/tests/unit/with_dbs/03/workspaces/test_workspaces__folders_and_projects_crud.py index 44b015bec0d..c95aebe6fdd 100644 --- a/services/web/server/tests/unit/with_dbs/03/workspaces/test_workspaces__folders_and_projects_crud.py +++ b/services/web/server/tests/unit/with_dbs/03/workspaces/test_workspaces__folders_and_projects_crud.py @@ -145,7 +145,7 @@ async def test_workspaces_full_workflow_with_folders_and_projects( resp = await client.get(url) _, errors = await assert_status( resp, - status.HTTP_401_UNAUTHORIZED, + status.HTTP_403_FORBIDDEN, ) assert errors From 0111cb95af7a175f192ca560df3e8ec169f01391 Mon Sep 17 00:00:00 2001 From: Sylvain <35365065+sanderegg@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:44:49 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=94=A8Fix=20codecov4=20(#6610)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codecov.yml | 8 +- .github/workflows/ci-testing-deploy.yml | 165 ++++++++++-------------- api/tests/Makefile | 19 +++ ci/github/unit-testing/api.bash | 21 +-- ci/github/unit-testing/frontend.bash | 74 ----------- 5 files changed, 103 insertions(+), 184 deletions(-) delete mode 100755 ci/github/unit-testing/frontend.bash diff --git a/.codecov.yml b/.codecov.yml index 8072681d00f..6e9caac4b87 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,6 +13,7 @@ flag_management: threshold: 1% - type: patch target: auto + threshold: 1% component_management: @@ -31,7 +32,7 @@ component_management: - packages/aws-library/** - component_id: pkg_dask_task_models_library paths: - - packages/dask-task-library/** + - packages/dask-task-models-library/** - component_id: pkg_models_library paths: - packages/models-library/** @@ -92,9 +93,6 @@ component_management: - component_id: invitations paths: - services/invitations/** - - component_id: migration - paths: - - services/migration/** - component_id: osparc_gateway_server paths: - services/osparc-gateway-server/** @@ -119,10 +117,12 @@ coverage: project: default: informational: true + threshold: 1% patch: default: informational: true + threshold: 1% comment: layout: "header,diff,flags,components,footer" diff --git a/.github/workflows/ci-testing-deploy.yml b/.github/workflows/ci-testing-deploy.yml index 31f12fc1acb..86633d0b3c8 100644 --- a/.github/workflows/ci-testing-deploy.yml +++ b/.github/workflows/ci-testing-deploy.yml @@ -345,10 +345,10 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/webserver.bash typecheck - name: test isolated - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/webserver.bash test_isolated - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/webserver.bash test_with_db 01 - uses: codecov/codecov-action@v4.6.0 env: @@ -489,7 +489,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/storage.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/storage.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -540,7 +540,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/agent.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/agent.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -588,6 +588,16 @@ jobs: run: ./ci/github/unit-testing/api.bash install - name: test run: ./ci/github/unit-testing/api.bash test + - uses: codecov/codecov-action@v4.6.0 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + flags: unittests #optional + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} unit-test-api-server: needs: changes @@ -625,10 +635,10 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/api-server.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/api-server.bash test - name: OAS backwards compatibility check - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/api-server.bash openapi-diff - uses: codecov/codecov-action@v4.6.0 env: @@ -677,7 +687,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/autoscaling.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/autoscaling.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -726,10 +736,10 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/catalog.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/catalog.bash test - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs @@ -789,7 +799,7 @@ jobs: pushd services/clusters-keeper && \ make mypy - name: test - if: always() + if: ${{ !cancelled() }} run: | source .venv/bin/activate && \ pushd services/clusters-keeper && \ @@ -841,10 +851,10 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/datcore-adapter.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/datcore-adapter.bash test - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs @@ -938,10 +948,10 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/director-v2.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/director-v2.bash test - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs @@ -993,7 +1003,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/aws-library.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/aws-library.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1042,7 +1052,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/dask-task-models-library.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/dask-task-models-library.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1091,7 +1101,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/dask-sidecar.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/dask-sidecar.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1147,7 +1157,7 @@ jobs: pushd services/osparc-gateway-server && \ make mypy - name: test - if: always() + if: ${{ !cancelled() }} run: | source .venv/bin/activate && \ pushd services/osparc-gateway-server && \ @@ -1199,7 +1209,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/payments.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/payments.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1248,7 +1258,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/dynamic-scheduler.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/dynamic-scheduler.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1304,7 +1314,7 @@ jobs: pushd services/resource-usage-tracker && \ make mypy - name: test - if: always() + if: ${{ !cancelled() }} run: | source .venv/bin/activate && \ pushd services/resource-usage-tracker && \ @@ -1356,7 +1366,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/dynamic-sidecar.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/dynamic-sidecar.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1413,7 +1423,7 @@ jobs: pushd services/efs-guardian && \ make mypy - name: test - if: always() + if: ${{ !cancelled() }} run: | source .venv/bin/activate && \ pushd services/efs-guardian && \ @@ -1429,42 +1439,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - unit-test-frontend: - needs: changes - if: ${{ needs.changes.outputs.static-webserver == 'true' || github.event_name == 'push' }} - timeout-minutes: 18 # if this timeout gets too small, then split the tests - name: "[unit] frontend" - runs-on: ${{ matrix.os }} - strategy: - matrix: - node: [14] - os: [ubuntu-22.04] - fail-fast: false - steps: - - uses: actions/checkout@v4 - - name: setup docker buildx - id: buildx - uses: docker/setup-buildx-action@v3 - with: - driver: docker-container - - uses: actions/setup-node@v4.0.4 - with: - node-version: ${{ matrix.node }} - cache: "npm" - - name: install uv - uses: yezz123/setup-uv@v4 - - uses: actions/cache@v4 - id: cache-uv - with: - path: ~/.cache/uv - key: ${{ runner.os }}-${{ github.job }}-python-${{ matrix.python }}-uv - - name: show system version - run: ./ci/helpers/show_system_versions.bash - - name: install - run: ./ci/github/unit-testing/frontend.bash install - - name: test - run: ./ci/github/unit-testing/frontend.bash test - unit-test-python-linting: needs: changes if: ${{ needs.changes.outputs.anything-py == 'true' || github.event_name == 'push' }} @@ -1537,7 +1511,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/postgres-database.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/postgres-database.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1586,7 +1560,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/invitations.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/invitations.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1635,7 +1609,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/service-integration.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/service-integration.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1684,7 +1658,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/service-library.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/service-library.bash test_all - uses: codecov/codecov-action@v4.6.0 env: @@ -1733,7 +1707,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/settings-library.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/settings-library.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1834,7 +1808,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/notifications-library.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/notifications-library.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1885,7 +1859,7 @@ jobs: - name: typecheck run: ./ci/github/unit-testing/simcore-sdk.bash typecheck - name: test - if: always() + if: ${{ !cancelled() }} run: ./ci/github/unit-testing/simcore-sdk.bash test - uses: codecov/codecov-action@v4.6.0 env: @@ -1917,7 +1891,6 @@ jobs: unit-test-director, unit-test-dynamic-sidecar, unit-test-efs-guardian, - unit-test-frontend, unit-test-models-library, unit-test-notifications-library, unit-test-osparc-gateway-server, @@ -1995,13 +1968,13 @@ jobs: - name: test run: ./ci/github/integration-testing/webserver.bash test 01 - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/web/server/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/webserver.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2059,13 +2032,13 @@ jobs: - name: test run: ./ci/github/integration-testing/webserver.bash test 02 - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/web/server/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/webserver.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2123,13 +2096,13 @@ jobs: - name: test run: ./ci/github/integration-testing/director-v2.bash test 01 - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/director-v2/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/director-v2.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2191,13 +2164,13 @@ jobs: - name: test run: ./ci/github/integration-testing/director-v2.bash test 02 - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/director-v2/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/director-v2.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2257,13 +2230,13 @@ jobs: - name: test run: ./ci/github/integration-testing/dynamic-sidecar.bash test 01 - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/dynamic-sidecar/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/dynamic-sidecar.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2334,13 +2307,13 @@ jobs: pushd services/osparc-gateway-server && \ make test-system - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./services/director-v2/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: | pushd services/osparc-gateway-server && \ make down @@ -2400,13 +2373,13 @@ jobs: - name: test run: ./ci/github/integration-testing/simcore-sdk.bash test - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./packages/simcore-sdk/test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/integration-testing/simcore-sdk.bash clean_up - uses: codecov/codecov-action@v4.6.0 env: @@ -2416,7 +2389,7 @@ jobs: integration-tests: # NOTE: this is a github required status check! - if: always() + if: ${{ !cancelled() }} needs: [ integration-test-director-v2-01, @@ -2490,13 +2463,13 @@ jobs: - name: test run: ./ci/github/system-testing/public-api.bash test - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/public-api.bash clean_up system-test-swarm-deploy: @@ -2556,13 +2529,13 @@ jobs: name: ${{ github.job }}_services_settings_schemas path: ./services/**/settings-schema.json - name: upload failed tests logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./test_failures - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/swarm-deploy.bash clean_up system-test-e2e: @@ -2622,28 +2595,28 @@ jobs: run: ./ci/github/system-testing/e2e.bash test - name: dump docker logs id: docker_logs_dump - if: failure() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/e2e.bash dump_docker_logs - name: upload docker logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./tests/e2e/test_failures - name: upload screenshots - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_screenshots path: tests/e2e/screenshots - name: upload e2e logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_logs path: tests/e2e/logs - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/e2e.bash clean_up system-test-e2e-playwright: @@ -2699,16 +2672,16 @@ jobs: ./ci/github/system-testing/e2e-playwright.bash test - name: dump docker logs id: docker_logs_dump - if: failure() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/e2e-playwright.bash dump_docker_logs - name: upload docker logs - if: failure() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_docker_logs path: ./tests/e2e-playwright/test_failures - name: upload tracing if failed - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{ github.job }}_tracing @@ -2748,12 +2721,12 @@ jobs: - name: test run: ./ci/github/system-testing/environment-setup.bash test - name: cleanup - if: always() + if: ${{ !cancelled() }} run: ./ci/github/system-testing/environment-setup.bash clean_up system-tests: # NOTE: this is a github required status check! - if: always() + if: ${{ !cancelled() }} needs: [ system-test-e2e, diff --git a/api/tests/Makefile b/api/tests/Makefile index f732b2fae17..01b0c981585 100644 --- a/api/tests/Makefile +++ b/api/tests/Makefile @@ -26,6 +26,25 @@ test-dev: _check_venv_active ## runs all tests [DEV] # running unit tests pytest -vv --exitfirst --failed-first --durations=10 --pdb $(CURDIR) +.PHONY: test-ci +test-ci: _check_venv_active ## runs all tests [DEV] + # running unit tests + pytest \ + --asyncio-mode=auto \ + --color=yes \ + --cov-append \ + --cov-config=.coveragerc \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=junit.xml -o junit_family=legacy \ + --cov=api \ + --durations=10 \ + --log-date-format="%Y-%m-%d %H:%M:%S" \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --verbose \ + -m "not heavy_load" \ + $(PYTEST_ADDITIONAL_PARAMETERS) \ + $(TEST_TARGET) .PHONY: help diff --git a/ci/github/unit-testing/api.bash b/ci/github/unit-testing/api.bash index e5806f24ff6..eb0db9e62fb 100755 --- a/ci/github/unit-testing/api.bash +++ b/ci/github/unit-testing/api.bash @@ -6,20 +6,21 @@ set -o pipefail # don't hide errors within pipes IFS=$'\n\t' install() { - bash ci/helpers/ensure_python_pip.bash - pip3 install --requirement api/tests/requirements.txt + make devenv + # shellcheck source=/dev/null + source .venv/bin/activate + pushd api/tests + make install + popd uv pip list } test() { - pytest \ - --color=yes \ - --durations=10 \ - --log-date-format="%Y-%m-%d %H:%M:%S" \ - --log-format="%(asctime)s %(levelname)s %(message)s" \ - --verbose \ - -m "not heavy_load" \ - api/tests + # shellcheck source=/dev/null + source .venv/bin/activate + pushd api/tests + make test-ci + popd } # Check if the function exists (bash specific) diff --git a/ci/github/unit-testing/frontend.bash b/ci/github/unit-testing/frontend.bash deleted file mode 100755 index cffd77cf64a..00000000000 --- a/ci/github/unit-testing/frontend.bash +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash -# http://redsymbol.net/articles/unofficial-bash-strict-mode/ -set -o errexit # abort on nonzero exitstatus -set -o nounset # abort on unbound variable -set -o pipefail # don't hide errors within pipes -IFS=$'\n\t' - -install() { - npm install - make -C services/static-webserver/client clean - npx eslint --version - make -C services/static-webserver/client info -} - -test() { - echo "# Running Linter" - npm run linter - - pushd services/static-webserver/client - - echo "# Building build version" - make compile - - echo "# Building source version" - make compile-dev flags=--machine-readable - - echo "# Serving source version" - make serve-dev flags="--machine-readable --target=source --listen-port=8080" detached=test-server - - #TODO: move this inside qx-kit container - echo "# Waiting for build to complete" - while ! nc -z localhost 8080; do - sleep 1 # wait for 10 second before check again - done - - # FIXME: reports ERROR ReferenceError: URL is not defined. See https://github.com/ITISFoundation/osparc-simcore/issues/1071 - ## node source-output/resource/qxl/testtapper/run.js --diag --verbose http://localhost:8080/testtapper - wget --spider http://localhost:8080/ - - make clean - popd - - #TODO: no idea what is this doing... disabled at the moment since travis is supposed to do it as well - - # # prepare documentation site ... - # git clone --depth 1 https://github.com/ITISFoundation/itisfoundation.github.io.git - # rm -rf itisfoundation.github.io/.git - - # # if we have old cruft hanging around, we should remove all this will - # # only trigger once - # if [ -d itisfoundation.github.io/transpiled ]; then - # rm -rf itisfoundation.github.io/* - # fi - - # # add the default homepage - # cp -rp docs/webdocroot/* itisfoundation.github.io - - # # add our build - # if [ -d services/static-webserver/client/build-output ]; then - # rm -rf itisfoundation.github.io/frontend - # cp -rp services/static-webserver/client/build-output itisfoundation.github.io/frontend - # fi -} - -# Check if the function exists (bash specific) -if declare -f "$1" > /dev/null -then - # call arguments verbatim - "$@" -else - # Show a helpful error - echo "'$1' is not a known function name" >&2 - exit 1 -fi From 61fc24a85ee48a07c0c985601085731bf3b0856d Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:44:43 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=8E=A8=20[S4L]=20New=20studies:=20Poi?= =?UTF-8?q?nt=20to=20the=20upgraded=20key=20services=20(#6611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/source/resource/osparc/new_studies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/resource/osparc/new_studies.json b/services/static-webserver/client/source/resource/osparc/new_studies.json index cac8acd2f8f..a13a042051f 100644 --- a/services/static-webserver/client/source/resource/osparc/new_studies.json +++ b/services/static-webserver/client/source/resource/osparc/new_studies.json @@ -119,7 +119,7 @@ "s4l": { "linkedResource": "services", "resources": [{ - "expectedKey": "simcore/services/dynamic/sim4life-8-0-0-dy", + "expectedKey": "simcore/services/dynamic/s4l-ui", "title": "Start Sim4Life", "description": "New Sim4Life project", "newStudyLabel": "New S4L project", @@ -129,7 +129,7 @@ "s4lacad": { "linkedResource": "services", "resources": [{ - "expectedKey": "simcore/services/dynamic/sim4life-8-0-0-dy", + "expectedKey": "simcore/services/dynamic/s4l-ui", "title": "Start Sim4Life", "description": "New Sim4Life project", "newStudyLabel": "New S4L project", @@ -139,7 +139,7 @@ "s4llite": { "linkedResource": "services", "resources": [{ - "expectedKey": "simcore/services/dynamic/sim4life-lite", + "expectedKey": "simcore/services/dynamic/s4l-ui-lite", "title": "Start ${replace_me_product_name}", "description": "New project", "newStudyLabel": "New project", From be8e3e933fe9c990838528277ffdfda15d1ae0e5 Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:29:55 +0100 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=90=9B=20[Frontend]=20Initialize=20St?= =?UTF-8?q?udy=20Browser=20container=20spacing=20in=20list=20mode=20(#6613?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ResourceContainerManager.js | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js index df41744539d..f066e10b56e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -41,13 +41,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { this._add(foldersContainer); foldersContainer.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); - const nonGroupedContainer = this.__nonGroupedContainer = new osparc.dashboard.ToggleButtonContainer(); - [ - "changeSelection", - "changeVisibility" - ].forEach(signalName => { - nonGroupedContainer.addListener(signalName, e => this.fireDataEvent(signalName, e.getData()), this); - }); + const nonGroupedContainer = this.__nonGroupedContainer = this.__createFlatList(); this._add(nonGroupedContainer); const groupedContainers = this.__groupedContainers = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); @@ -306,21 +300,30 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { this.__groupedContainers.add(noGroupContainer); this._add(this.__groupedContainers); } else { - const flatList = this.__nonGroupedContainer = new osparc.dashboard.ToggleButtonContainer(); + const flatList = this.__nonGroupedContainer = this.__createFlatList(); osparc.utils.Utils.setIdToWidget(flatList, resourceType + "List"); - [ - "changeSelection", - "changeVisibility" - ].forEach(signalName => { - flatList.addListener(signalName, e => this.fireDataEvent(signalName, e.getData()), this); - }); + this._add(flatList); + } + }, + + __createFlatList: function() { + const flatList = new osparc.dashboard.ToggleButtonContainer(); + const setContainerSpacing = () => { const spacing = this.getMode() === "grid" ? osparc.dashboard.GridButtonBase.SPACING : osparc.dashboard.ListButtonBase.SPACING; - this.__nonGroupedContainer.getLayout().set({ + flatList.getLayout().set({ spacingX: spacing, spacingY: spacing }); - this._add(this.__nonGroupedContainer); - } + }; + setContainerSpacing(); + this.addListener("changeMode", () => setContainerSpacing()); + [ + "changeSelection", + "changeVisibility" + ].forEach(signalName => { + flatList.addListener(signalName, e => this.fireDataEvent(signalName, e.getData()), this); + }); + return flatList; }, reloadCards: function(resourceType) { From 59f072564f16fb0ac420a37e6880913c297a3b05 Mon Sep 17 00:00:00 2001 From: Sylvain <35365065+sanderegg@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:41:23 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=E2=99=BB=EF=B8=8FReduce=20noisyness=20of?= =?UTF-8?q?=20gunicorn=20and=20socketio=20in=20webserver=20logs=20(#6616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/web/server/src/simcore_service_webserver/log.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/log.py b/services/web/server/src/simcore_service_webserver/log.py index a32c44d41ff..6635518d091 100644 --- a/services/web/server/src/simcore_service_webserver/log.py +++ b/services/web/server/src/simcore_service_webserver/log.py @@ -13,11 +13,13 @@ "aio_pika", "aiormq", "engineio", - "openapi_spec_validator", - "sqlalchemy", - "sqlalchemy.engine", "inotify.adapters", + "gunicorn.access", + "openapi_spec_validator", "servicelib.aiohttp.monitoring", + "sqlalchemy.engine", + "sqlalchemy", + "socketio", ) From f870beb88ec0aa262f4d59deccf098f96dfb8cd4 Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:23:04 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=90=9B=20[Frontend]=20Fix:=20allow=20?= =?UTF-8?q?moving=20to=20root=20folder=20(#6615)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js index 0b43298c923..91499e44ce7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -471,7 +471,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { _moveFolderToRequested: function(folderId) { const currentWorkspaceId = this.getCurrentWorkspaceId(); - const currentFolderId = this.getCurrentWorkspaceId(); + const currentFolderId = this.getCurrentFolderId(); const moveFolderTo = new osparc.dashboard.MoveResourceTo(currentWorkspaceId, currentFolderId); const title = this.tr("Move to..."); const win = osparc.ui.window.Window.popUpInWindow(moveFolderTo, title, 400, 400); @@ -481,7 +481,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const destWorkspaceId = data["workspaceId"]; const destFolderId = data["folderId"]; if (destWorkspaceId !== currentWorkspaceId) { - const msg = this.tr("Coming soon"); + const msg = this.tr("Moving folders to Shared Workspaces are coming soon"); osparc.FlashMessenger.getInstance().logAs(msg, "WARNING"); return; } @@ -504,6 +504,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, this); } }); + moveFolderTo.addListener("cancel", () => win.close()); }, __moveFolderToWorkspace: function(folderId, destWorkspaceId) { @@ -1415,7 +1416,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { moveToButton["moveToButton"] = true; moveToButton.addListener("tap", () => { const currentWorkspaceId = this.getCurrentWorkspaceId(); - const currentFolderId = this.getCurrentWorkspaceId(); + const currentFolderId = this.getCurrentFolderId(); const moveStudyTo = new osparc.dashboard.MoveResourceTo(currentWorkspaceId, currentFolderId); const title = this.tr("Move to..."); const win = osparc.ui.window.Window.popUpInWindow(moveStudyTo, title, 400, 400);