From 4721187ebb2b30832d2bbef393e8726d808d8279 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 4 Sep 2024 15:13:41 +0200 Subject: [PATCH 01/58] workspace improvements --- .../api_schemas_webserver/folders_v2.py | 1 + .../api_schemas_webserver/projects.py | 2 + .../folders/_folders_api.py | 4 ++ .../projects/_crud_api_create.py | 69 ++++++++++++++----- .../projects/_crud_api_read.py | 21 +++++- .../projects/_crud_handlers.py | 13 ---- .../projects/_folders_api.py | 6 +- .../workspaces/api.py | 8 ++- 8 files changed, 86 insertions(+), 38 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py b/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py index a3e52b0d7b9..3e0aaf0e05d 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py @@ -18,6 +18,7 @@ class FolderGet(OutputSchema): created_at: datetime modified_at: datetime owner: GroupID + workspace_id: WorkspaceID | None class FolderGetPage(NamedTuple): 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 fdfe4fb0666..fb43294320d 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 @@ -7,6 +7,7 @@ from typing import Any, Literal, TypeAlias +from models_library.folders import FolderID from models_library.workspaces import WorkspaceID from pydantic import Field, validator @@ -41,6 +42,7 @@ class ProjectCreateNew(InputSchema): classifiers: list[ClassifierID] = Field(default_factory=list) ui: StudyUI | None = None workspace_id: WorkspaceID | None = None + folder_id: FolderID | None = None _empty_is_none = validator( "uuid", "thumbnail", "description", "workspace_id", allow_reuse=True, pre=True diff --git a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py index f54b3a9f4c2..bd8263971e8 100644 --- a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py +++ b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py @@ -86,6 +86,7 @@ async def create_folder( created_at=folder_db.created, modified_at=folder_db.modified, owner=folder_db.created_by_gid, + workspace_id=workspace_id, ) @@ -124,6 +125,7 @@ async def get_folder( created_at=folder_db.created, modified_at=folder_db.modified, owner=folder_db.created_by_gid, + workspace_id=folder_db.workspace_id, ) @@ -178,6 +180,7 @@ async def list_folders( created_at=folder.created, modified_at=folder.modified, owner=folder.created_by_gid, + workspace_id=folder.workspace_id, ) for folder in folders ], @@ -233,6 +236,7 @@ async def update_folder( created_at=folder_db.created, modified_at=folder_db.modified, owner=folder_db.created_by_gid, + workspace_id=folder_db.workspace_id, ) diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py index 780c976bd45..4a14ecd62a3 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py @@ -14,7 +14,6 @@ from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from models_library.utils.json_serialization import json_dumps -from models_library.workspaces import WorkspaceID from pydantic import parse_obj_as from servicelib.aiohttp.long_running_tasks.server import TaskProgress from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON @@ -27,13 +26,15 @@ from ..application_settings import get_application_settings from ..catalog import client as catalog_client from ..director_v2 import api +from ..folders import _folders_db as folders_db from ..storage.api import ( copy_data_folders_from_project, get_project_total_size_simcore_s3, ) from ..users.api import get_user_fullname -from ..workspaces.api import get_workspace +from ..workspaces.api import check_user_workspace_access from ..workspaces.errors import WorkspaceAccessForbiddenError +from . import _folders_db as project_to_folders_db from . import projects_api from ._metadata_api import set_project_ancestors from ._permalink_api import update_or_pop_permalink_in_project @@ -233,7 +234,6 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche simcore_user_agent: str, parent_project_uuid: ProjectID | None, parent_node_id: NodeID | None, - workspace_id: WorkspaceID | None, ) -> None: """Implements TaskProtocol for 'create_projects' handler @@ -264,8 +264,30 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche project_nodes = None try: task_progress.update(message="creating new study...") + + workspace_id = None + folder_id = None + if predefined_project: + if workspace_id := predefined_project.get("workspaceId", None): + await check_user_workspace_access( + request.app, + user_id=user_id, + workspace_id=workspace_id, + product_name=product_name, + permission="write", + ) + if folder_id := predefined_project.get("folderId", None): + # Check user has access to folder + await folders_db.get_for_user_or_workspace( + request.app, + folder_id=folder_id, + product_name=product_name, + user_id=user_id if workspace_id is None else None, + workspace_id=workspace_id, + ) + if from_study: - # 1. prepare copy + # 1.1 prepare copy ( new_project, project_node_coro, @@ -281,6 +303,18 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche if project_node_coro: project_nodes = await project_node_coro + # 1.2 does project belong to some folder? + prj_to_folder_db = await project_to_folders_db.get_project_to_folder( + request.app, + project_id=new_project["uuid"], + private_workspace_user_id_or_none=user_id + if workspace_id is None + else None, + ) + if prj_to_folder_db: + # As user has access to the project, it has implicitly access to the folder + folder_id = prj_to_folder_db.folder_id + if predefined_project: # 2. overrides with optional body and re-validate new_project, project_nodes = await _compose_project_data( @@ -290,21 +324,7 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche predefined_project=predefined_project, ) - # If user wants to create project in specific workspace - if workspace_id: - # Verify user access to the specified workspace; raise an error if access is denied - workspace = await get_workspace( - request.app, - user_id=user_id, - workspace_id=workspace_id, - product_name=product_name, - ) - if workspace.my_access_rights.write is False: - raise WorkspaceAccessForbiddenError( - reason=f"User {user_id} does not have write permission on workspace {workspace_id}." - ) - - # 3. save new project in DB + # 3.1 save new project in DB new_project = await db.insert_project( project=jsonable_encoder(new_project), user_id=user_id, @@ -323,6 +343,17 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche ) task_progress.update() + # 3.2 move project to proper folder + if folder_id: + await project_to_folders_db.insert_project_to_folder( + request.app, + project_id=new_project["uuid"], + folder_id=folder_id, + private_workspace_user_id_or_none=( + user_id if workspace_id is None else None + ), + ) + # 4. deep copy source project's files if copy_file_coro: # NOTE: storage needs to have access to the new project prior to copying files diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py index 2c9df781336..938f7f1b666 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py @@ -5,13 +5,15 @@ """ +from typing import Any + from aiohttp import web from models_library.api_schemas_webserver._base import OutputSchema from models_library.api_schemas_webserver.projects import ProjectListItem from models_library.folders import FolderID from models_library.projects import ProjectID from models_library.rest_ordering import OrderBy -from models_library.users import UserID +from models_library.users import GroupID, UserID from models_library.workspaces import WorkspaceID from pydantic import NonNegativeInt from servicelib.utils import logged_gather @@ -22,6 +24,7 @@ from ..catalog.client import get_services_for_user_in_product from ..folders import _folders_db as folders_db +from ..workspaces import _workspaces_db as workspaces_db from . import projects_api from ._permalink_api import update_or_pop_permalink_in_project from .db import ProjectDBAPI @@ -34,6 +37,7 @@ async def _append_fields( user_id: UserID, project: ProjectDict, is_template: bool, + workspace_access_rights: dict[GroupID, Any] | None, model_schema_cls: type[OutputSchema], ): # state @@ -47,6 +51,12 @@ async def _append_fields( # permalink await update_or_pop_permalink_in_project(request, project) + # replace project access rights (if project is in workspace) + if workspace_access_rights: + project["accessRights"] = { + key: access.dict() for key, access in workspace_access_rights.items() + } + # validate return model_schema_cls.parse_obj(project).data(exclude_unset=True) @@ -106,6 +116,14 @@ async def list_projects( # pylint: disable=too-many-arguments workspace_id=workspace_id, ) + # If workspace, override project access rights + workspace_access_rights = None + if workspace_id: + workspace_db = await workspaces_db.get_workspace_for_user( + app, user_id=user_id, workspace_id=workspace_id, product_name=product_name + ) + workspace_access_rights = workspace_db.access_rights + projects: list[ProjectDict] = await logged_gather( *( _append_fields( @@ -113,6 +131,7 @@ async def list_projects( # pylint: disable=too-many-arguments user_id=user_id, project=prj, is_template=prj_type == ProjectTypeDB.TEMPLATE, + workspace_access_rights=workspace_access_rights, model_schema_cls=ProjectListItem, ) for prj, prj_type in zip(db_projects, db_project_types) 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 16e7f9574ad..9f138ebbb97 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 @@ -138,7 +138,6 @@ async def create_project(request: web.Request): # :create, :copy (w/ and w/o override) # NOTE: see clone_project - workspace_id = None if not request.can_read_body: # request w/o body predefined_project = None @@ -158,10 +157,6 @@ async def create_project(request: web.Request): or None ) - # # Manually include workspace after exclude - # workspace_id = project_create.dict(by_alias=True).get("workspaceId", None) - # predefined_project["workspaceId"] = workspace_id - return await start_long_running_task( request, _crud_api_create.create_project, # type: ignore[arg-type] # @GitHK, @pcrespov this one I don't know how to fix @@ -179,7 +174,6 @@ async def create_project(request: web.Request): predefined_project=predefined_project, parent_project_uuid=header_params.parent_project_uuid, parent_node_id=header_params.parent_node_id, - workspace_id=workspace_id, ) @@ -631,12 +625,6 @@ async def clone_project(request: web.Request): req_ctx = RequestContext.parse_obj(request) path_params = parse_request_path_parameters_as(ProjectPathParams, request) - db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(request.app) - try: - project_db = await db.get_project_db(path_params.project_id) - except ProjectNotFoundError as exc: - raise web.HTTPNotFound(reason=f"Project {exc.project_uuid} not found") from exc - return await start_long_running_task( request, _crud_api_create.create_project, # type: ignore[arg-type] # @GitHK, @pcrespov this one I don't know how to fix @@ -656,5 +644,4 @@ async def clone_project(request: web.Request): predefined_project=None, parent_project_uuid=None, parent_node_id=None, - workspace_id=project_db.workspace_id, ) diff --git a/services/web/server/src/simcore_service_webserver/projects/_folders_api.py b/services/web/server/src/simcore_service_webserver/projects/_folders_api.py index d2107b46499..4b465edf0e9 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_folders_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_folders_api.py @@ -82,7 +82,7 @@ async def move_project_into_folder( app, project_id=project_id, folder_id=folder_id, - private_workspace_user_id_or_none=user_id - if workspace_is_private - else None, + private_workspace_user_id_or_none=( + user_id if workspace_is_private else None + ), ) diff --git a/services/web/server/src/simcore_service_webserver/workspaces/api.py b/services/web/server/src/simcore_service_webserver/workspaces/api.py index d3643161876..2b3ca3bbd42 100644 --- a/services/web/server/src/simcore_service_webserver/workspaces/api.py +++ b/services/web/server/src/simcore_service_webserver/workspaces/api.py @@ -1,6 +1,10 @@ # mypy: disable-error-code=truthy-function -from ._workspaces_api import get_workspace +from ._workspaces_api import check_user_workspace_access, get_workspace assert get_workspace # nosec +assert check_user_workspace_access # nosec -__all__: tuple[str, ...] = ("get_workspace",) +__all__: tuple[str, ...] = ( + "get_workspace", + "check_user_workspace_access", +) From 365c4174bffb60f1b6b7e117f5983639fc2b0482 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 4 Sep 2024 15:14:40 +0200 Subject: [PATCH 02/58] open api specs --- .../simcore_service_webserver/api/v0/openapi.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 63abbd2dbd5..721983bb231 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 @@ -3323,7 +3323,7 @@ paths: '403': description: ProjectInvalidRightsError '404': - description: UserDefaultWalletNotFoundError, ProjectNotFoundError + description: ProjectNotFoundError, UserDefaultWalletNotFoundError '409': description: ProjectTooManyProjectOpenedError '422': @@ -8059,6 +8059,11 @@ components: exclusiveMinimum: true type: integer minimum: 0 + workspaceId: + title: Workspaceid + exclusiveMinimum: true + type: integer + minimum: 0 GenerateInvitation: title: GenerateInvitation required: @@ -8390,7 +8395,6 @@ components: issuer: title: Issuer type: string - format: email guest: title: Guest type: string @@ -9825,6 +9829,11 @@ components: exclusiveMinimum: true type: integer minimum: 0 + folderId: + title: Folderid + exclusiveMinimum: true + type: integer + minimum: 0 ProjectGet: title: ProjectGet required: From 78f5c40e7125dc732382a3bc180d7a97eee5daad Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 4 Sep 2024 15:20:06 +0200 Subject: [PATCH 03/58] workspace improvements --- .../src/simcore_service_webserver/projects/_crud_api_read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py index 938f7f1b666..bb6850e0402 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py @@ -5,9 +5,9 @@ """ -from typing import Any from aiohttp import web +from models_library.access_rights import AccessRights from models_library.api_schemas_webserver._base import OutputSchema from models_library.api_schemas_webserver.projects import ProjectListItem from models_library.folders import FolderID @@ -37,7 +37,7 @@ async def _append_fields( user_id: UserID, project: ProjectDict, is_template: bool, - workspace_access_rights: dict[GroupID, Any] | None, + workspace_access_rights: dict[GroupID, AccessRights] | None, model_schema_cls: type[OutputSchema], ): # state From f60e7ccfad31a2fdabdefe129563f8d880b97e39 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 4 Sep 2024 17:14:59 +0200 Subject: [PATCH 04/58] daily work --- .../workspaces/_workspaces_db.py | 4 +- services/web/server/tests/conftest.py | 1 + .../02/test_projects_crud_handlers__clone.py | 39 ++++++++++++ .../unit/with_dbs/03/folders/test_folders.py | 1 + ...t_workspaces__folders_and_projects_crud.py | 60 +++++++++++++++++++ 5 files changed, 102 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py b/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py index ac13e33581e..98ef90886ac 100644 --- a/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py +++ b/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py @@ -132,9 +132,7 @@ async def list_workspaces_for_user( my_access_rights_subquery.c.my_access_rights, ) .select_from( - workspaces.join(access_rights_subquery, isouter=True).join( - my_access_rights_subquery - ) + workspaces.join(access_rights_subquery).join(my_access_rights_subquery) ) .where(workspaces.c.product_name == product_name) ) diff --git a/services/web/server/tests/conftest.py b/services/web/server/tests/conftest.py index 4c9a38a7b0d..3f47a75afc2 100644 --- a/services/web/server/tests/conftest.py +++ b/services/web/server/tests/conftest.py @@ -226,6 +226,7 @@ async def _setup( "name": None, "prjOwner": None, "workspaceId": None, + "folderId": None, } if from_study: # access rights are replaced diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py index be67e5c5bc9..2c86badf9fb 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py @@ -107,3 +107,42 @@ async def test_clone_invalid_project_responds_not_found( await _request_clone_project(client, url) assert err_info.value.status == HTTPNotFound.status_code + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_clone_project_inside_a_workspace_and_folder( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + # mocks backend + storage_subsystem_mock: MockedStorageSubsystem, + mock_catalog_service_api_responses: None, + project_db_cleaner: None, +): + assert client.app + + # 1. create workspace + + # 2. create folder inside a workspace + + # 3. add a project inside the folder + + url = client.app.router["clone_project"].url_for(project_id=project["uuid"]) + assert f"/v0/projects/{project['uuid']}:clone" == url.path + + cloned_project = await _request_clone_project(client, url) + + # check whether it's a clone + assert ProjectID(project["uuid"]) != cloned_project.uuid + assert project["description"] == cloned_project.description + assert parse_obj_as(datetime, project["creationDate"]) < parse_obj_as( + datetime, cloned_project.creation_date + ) + + assert len(project["workbench"]) == len(cloned_project.workbench) + assert set(project["workbench"].keys()) != set( + cloned_project.workbench.keys() + ), "clone does NOT preserve node ids" 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 acd3b1f96b9..8d84fcec39d 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 @@ -74,6 +74,7 @@ async def test_folders_full_workflow( ) resp = await client.get(url) data, _ = await assert_status(resp, status.HTTP_200_OK) + assert isinstance(data, FolderGet) assert data["folderId"] == added_folder["folderId"] assert data["name"] == "My first folder" 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 545f28d1b58..5c3007977f3 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 @@ -225,3 +225,63 @@ async def test_workspaces_full_workflow_with_folders_and_projects( resp = await client.get(url) data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 + + +@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) +async def test_workspaces_full_workflow_with_folders_and_projects2( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + expected: HTTPStatus, + mock_catalog_api_get_services_for_user_in_product: MockerFixture, + fake_project: ProjectDict, + workspaces_clean_db: None, +): + assert client.app + + # create a new workspace + url = client.app.router["create_workspace"].url_for() + resp = await client.post( + url.path, + json={ + "name": "My first workspace", + "description": "Custom description", + "thumbnail": None, + }, + ) + added_workspace, _ = await assert_status(resp, status.HTTP_201_CREATED) + + # Create folder in workspace + url = client.app.router["create_folder"].url_for() + resp = await client.post( + url.path, + json={ + "name": "Original user folder", + "workspaceId": f"{added_workspace['workspaceId']}", + }, + ) + added_folder, _ = await assert_status(resp, status.HTTP_201_CREATED) + + # Create project in workspace and folder + project_data = deepcopy(fake_project) + project_data["workspace_id"] = f"{added_workspace['workspaceId']}" + project_data["folder_id"] = f"{added_folder['folderId']}" + project = await create_project( + client.app, + project_data, + user_id=logged_user["id"], + product_name="osparc", + ) + + # List project in workspace and folder + base_url = client.app.router["list_projects"].url_for() + url = base_url.with_query( + { + "workspace_id": f"{added_workspace['workspaceId']}", + "folder_id": f"{added_folder['folderId']}", + } + ) + resp = await client.get(url) + data, _ = await assert_status(resp, status.HTTP_200_OK) + assert len(data) == 1 + assert data[0]["uuid"] == project["uuid"] From b73b7c796722c8250299426bd0e7338034365238 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Wed, 4 Sep 2024 18:19:37 +0200 Subject: [PATCH 05/58] no description for folders --- .../osparc/dashboard/FolderButtonItem.js | 25 +---------------- .../class/osparc/dashboard/FolderButtonNew.js | 4 +-- .../class/osparc/dashboard/StudyBrowser.js | 4 ++- .../source/class/osparc/data/model/Folder.js | 8 ------ .../class/osparc/editor/FolderEditor.js | 28 ------------------- .../source/class/osparc/store/Folders.js | 8 +++--- 6 files changed, 9 insertions(+), 68 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js index d6a0d76769d..18b997e3fb3 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -73,12 +73,6 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { apply: "__applyTitle" }, - description: { - check: "String", - nullable: true, - apply: "__applyDescription" - }, - lastModified: { check: "Date", nullable: true, @@ -143,7 +137,6 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { folder.bind("folderId", this, "folderId"); folder.bind("parentId", this, "parentFolderId"); folder.bind("name", this, "title"); - folder.bind("description", this, "description"); folder.bind("lastModified", this, "lastModified"); this.__addMenuButton(); @@ -152,11 +145,6 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { __applyTitle: function(value) { const label = this.getChildControl("title"); label.setValue(value); - this.__updateTooltip(); - }, - - __applyDescription: function() { - this.__updateTooltip(); }, __applyLastModified: function(value) { @@ -180,22 +168,18 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { const newFolder = false; const folderEditor = new osparc.editor.FolderEditor(newFolder).set({ label: folder.getName(), - description: folder.getDescription() }); const title = this.tr("Edit Folder"); - const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 200); + const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 150); folderEditor.addListener("updateFolder", () => { const newName = folderEditor.getLabel(); - const newDescription = folderEditor.getDescription(); const updateData = { "name": newName, - "description": newDescription }; osparc.data.model.Folder.putFolder(this.getFolderId(), updateData) .then(() => { folder.set({ name: newName, - description: newDescription }); this.fireDataEvent("folderUpdated", folder.getFolderId()); }) @@ -213,13 +197,6 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { menuButton.setMenu(menu); }, - __updateTooltip: function() { - const toolTipText = this.getTitle() + (this.getDescription() ? "
" + this.getDescription() : ""); - this.set({ - toolTipText - }) - }, - __itemSelected: function(newVal) { if (newVal) { this.fireDataEvent("folderSelected", this.getFolderId()); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonNew.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonNew.js index e0b0eb1aef6..1f0e862c726 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonNew.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonNew.js @@ -80,13 +80,11 @@ qx.Class.define("osparc.dashboard.FolderButtonNew", { const newFolder = true; const folderEditor = new osparc.editor.FolderEditor(newFolder); const title = this.tr("New Folder"); - const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 200); + const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 150); folderEditor.addListener("createFolder", () => { const name = folderEditor.getLabel(); - const description = folderEditor.getDescription(); this.fireDataEvent("createFolder", { name, - description }); win.close(); }); 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 e9b61fadca7..a0a69209a71 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -458,7 +458,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { newFolderCard.subscribeToFilterGroup("searchBarFilter"); newFolderCard.addListener("createFolder", e => { const data = e.getData(); - osparc.store.Folders.getInstance().postFolder(data.name, data.description, currentFolder ? currentFolder.getFolderId() : null) + const parentFolderId = currentFolder ? currentFolder.getFolderId() : null; + const currentWorkspaceId = this.getCurrentWorkspaceId(); + osparc.store.Folders.getInstance().postFolder(data.name, parentFolderId, currentWorkspaceId) .then(() => this.__reloadFolders()) .catch(err => console.error(err)); }) diff --git a/services/static-webserver/client/source/class/osparc/data/model/Folder.js b/services/static-webserver/client/source/class/osparc/data/model/Folder.js index a3e1eb7ca90..687b3d8edc7 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Folder.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Folder.js @@ -32,7 +32,6 @@ qx.Class.define("osparc.data.model.Folder", { folderId: folderData.folderId, parentId: folderData.parentFolderId, name: folderData.name, - description: folderData.description, myAccessRights: folderData.myAccessRights, accessRights: folderData.accessRights, owner: folderData.owner, @@ -63,13 +62,6 @@ qx.Class.define("osparc.data.model.Folder", { event: "changeName" }, - description: { - check: "String", - nullable: true, - init: null, - event: "changeDescription" - }, - myAccessRights: { check: "Object", nullable: false, diff --git a/services/static-webserver/client/source/class/osparc/editor/FolderEditor.js b/services/static-webserver/client/source/class/osparc/editor/FolderEditor.js index 5080b96b9be..1948026e352 100644 --- a/services/static-webserver/client/source/class/osparc/editor/FolderEditor.js +++ b/services/static-webserver/client/source/class/osparc/editor/FolderEditor.js @@ -27,32 +27,17 @@ qx.Class.define("osparc.editor.FolderEditor", { const title = this.getChildControl("title"); title.setRequired(true); manager.add(title); - this.getChildControl("description"); newFolder ? this.getChildControl("create") : this.getChildControl("save"); this.addListener("appear", this.__onAppear, this); }, properties: { - gid: { - check: "Number", - init: 0, - nullable: false, - event: "changeGid" - }, - label: { check: "String", init: "", nullable: false, event: "changeLabel" - }, - - description: { - check: "String", - init: "", - nullable: false, - event: "changeDescription" } }, @@ -78,19 +63,6 @@ qx.Class.define("osparc.editor.FolderEditor", { this._add(control); break; } - case "description": { - control = new qx.ui.form.TextArea().set({ - font: "text-14", - placeholder: this.tr("Description"), - autoSize: true, - minHeight: 70, - maxHeight: 140 - }); - this.bind("description", control, "value"); - control.bind("value", this, "description"); - this._add(control); - break; - } case "create": { const buttons = this.getChildControl("buttonsLayout"); control = new osparc.ui.form.FetchButton(this.tr("Create")).set({ diff --git a/services/static-webserver/client/source/class/osparc/store/Folders.js b/services/static-webserver/client/source/class/osparc/store/Folders.js index 5b3459852b9..cb4f243e2c1 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -52,11 +52,11 @@ qx.Class.define("osparc.store.Folders", { }); }, - postFolder: function(name, description, parentId = null) { + postFolder: function(name, parentFolderId = null, workspaceId = null) { const newFolderData = { - parentFolderId: parentId, - name: name, - description: description || "", + name, + parentFolderId, + workspaceId, }; const params = { data: newFolderData From 4390da5fc2dc849171683090583cc98454ac6f65 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Wed, 4 Sep 2024 18:24:49 +0200 Subject: [PATCH 06/58] add workspace_id to request --- .../class/osparc/dashboard/StudyBrowser.js | 22 +++++++++---------- .../source/class/osparc/data/Resources.js | 14 ++++++------ 2 files changed, 18 insertions(+), 18 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 a0a69209a71..e51634b4731 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -578,14 +578,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; - const currentFolderId = this.getCurrentFolderId(); - params.url.folderId = currentFolderId; + params.url.workspaceIdId = this.getCurrentWorkspaceId(); + params.url.folderId = this.getCurrentFolderId(); if (params.url.orderBy) { - return osparc.data.Resources.fetch("studies", "getPageFolderSortBy", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPageSortBy", params, undefined, options); } else if (params.url.search) { - return osparc.data.Resources.fetch("studies", "getPageFolderSearch", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPageSearch", params, undefined, options); } - return osparc.data.Resources.fetch("studies", "getPageFolder", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPage", params, undefined, options); }, __getTextFilteredNextRequest: function(text) { @@ -605,9 +605,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; - const currentFolderId = this.getCurrentFolderId(); - params.url.folderId = currentFolderId; - return osparc.data.Resources.fetch("studies", "getPageFolderSearch", params, undefined, options); + params.url.workspaceId = this.getCurrentWorkspaceId(); + params.url.folderId = this.getCurrentFolderId(); + return osparc.data.Resources.fetch("studies", "getPageSearch", params, undefined, options); }, __getSortedByNextRequest: function() { @@ -627,9 +627,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; - const currentFolderId = this.getCurrentFolderId(); - params.url.folderId = currentFolderId; - return osparc.data.Resources.fetch("studies", "getPageFolderSortBy", params, undefined, options); + params.url.workspaceId = this.getCurrentWorkspaceId(); + params.url.folderId = this.getCurrentFolderId(); + return osparc.data.Resources.fetch("studies", "getPageSortBy", params, undefined, options); }, invalidateStudies: function() { diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 098d3239bfa..2346cef70cd 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -119,19 +119,19 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/projects?type=user" }, - getPageFolder: { + getPage: { method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&workspace_id={workspaceId}&folder_id={folderId}" }, - getPageFolderSearch: { + getPageSearch: { useCache: false, method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}&search={text}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&workspace_id={workspaceId}&folder_id={folderId}&search={text}" }, - getPageFolderSortBy: { + getPageSortBy: { useCache: false, method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}&order_by={orderBy}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&workspace_id={workspaceId}&folder_id={folderId}&order_by={orderBy}" }, getOne: { useCache: false, @@ -299,7 +299,7 @@ qx.Class.define("osparc.data.Resources", { endpoints: { getPage: { method: "GET", - url: statics.API + "/folders?folder_id={folderId}&offset={offset}&limit={limit}" + url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}" }, getOne: { method: "GET", From c320719a9539471b981155198970984ca6e696ae Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 08:32:10 +0200 Subject: [PATCH 07/58] crud workspaces --- .../osparc/dashboard/WorkspaceButtonItem.js | 6 +-- .../class/osparc/data/model/Workspace.js | 4 -- .../source/class/osparc/store/Workspaces.js | 44 ++----------------- 3 files changed, 4 insertions(+), 50 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index 176d2775f46..3c00d2600a2 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -197,12 +197,8 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { "name": newName, "description": newDescription }; - osparc.data.model.Workspace.putWorkspace(this.getWorkspaceId(), updateData) + osparc.store.Workspaces.putWorkspace(this.getWorkspaceId(), updateData) .then(() => { - workspace.set({ - name: newName, - description: newDescription - }); this.fireDataEvent("workspaceUpdated", workspace.getWorkspaceId()); }) .catch(err => console.error(err)); diff --git a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js index ea4c324e9aa..bdcef9ba779 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js @@ -99,10 +99,6 @@ qx.Class.define("osparc.data.model.Workspace", { }, statics: { - putWorkspace: function(workspaceId, propKey, value) { - return osparc.store.Workspaces.putWorkspace(workspaceId, propKey, value); - }, - getProperties: function() { return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Workspace)); } diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 6d0664abb0a..cae9e859583 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -125,28 +125,14 @@ qx.Class.define("osparc.store.Workspaces", { }); } - /* - return osparc.data.Resources.getInstance().getAllPages("workspaces", params) + return osparc.data.Resources.getInstance().getAllPages("workspaces") .then(workspacesData => { - const workspaces = []; workspacesData.forEach(workspaceData => { const workspace = new osparc.data.model.Workspace(workspaceData); this.__addToCache(workspace); - workspaces.push(workspace); }); - return workspaces; + return this.workspacesCached; }); - */ - - return new Promise(resolve => { - if (this.workspacesCached.length === 0) { - this.self().FAKE_WORKSPACES.forEach(workspaceData => { - const workspace = new osparc.data.model.Workspace(workspaceData); - this.__addToCache(workspace); - }); - } - resolve(this.workspacesCached); - }); }, createNewWorkspaceData: function(name, description = "", thumbnail = "") { @@ -158,7 +144,6 @@ qx.Class.define("osparc.store.Workspaces", { }, postWorkspace: function(newWorkspaceData) { - /* const params = { data: newWorkspaceData }; @@ -168,30 +153,10 @@ qx.Class.define("osparc.store.Workspaces", { this.__addToCache(newWorkspace); return newWorkspace; }); - */ - const workspaceData = newWorkspaceData; - workspaceData["workspaceId"] = Math.floor(Math.random() * 100) + 100; - workspaceData["myAccessRights"] = osparc.share.CollaboratorsWorkspace.getOwnerAccessRight(); - const myGroupId = osparc.auth.Data.getInstance().getGroupId(); - workspaceData["accessRights"] = {}; - workspaceData["accessRights"][myGroupId] = osparc.share.CollaboratorsWorkspace.getOwnerAccessRight(); - workspaceData["createdAt"] = new Date().toISOString(); - workspaceData["lastModified"] = new Date().toISOString(); - return new Promise(resolve => { - const workspace = new osparc.data.model.Workspace(workspaceData); - this.__addToCache(workspace); - resolve(workspace); - }); }, deleteWorkspace: function(workspaceId) { return new Promise((resolve, reject) => { - if (this.__deleteFromCache(workspaceId)) { - resolve(); - } else { - reject(); - } - /* const params = { "url": { workspaceId @@ -206,7 +171,6 @@ qx.Class.define("osparc.store.Workspaces", { } }) .catch(err => reject(err)); - */ }); }, @@ -229,9 +193,7 @@ qx.Class.define("osparc.store.Workspaces", { } }); workspace.setLastModified(new Date()); - this.__deleteFromCache(workspaceId); - this.__addToCache(workspace); - resolve(); + resolve(workspace); }) .catch(err => reject(err)); }); From d96aa3b183da919d9b88b30f7fc3d27d70da809a Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 08:32:45 +0200 Subject: [PATCH 08/58] remove fake workspaces --- .../source/class/osparc/store/Workspaces.js | 89 ------------------- 1 file changed, 89 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index cae9e859583..781594612be 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -29,95 +29,6 @@ qx.Class.define("osparc.store.Workspaces", { return source+iconsSize; }, - FAKE_WORKSPACES: [{ - workspaceId: 1, - name: "Workspace 1", - description: "Workspace 1 desc", - thumbnail: "https://images.ctfassets.net/hrltx12pl8hq/01rJn4TormMsGQs1ZRIpzX/16a1cae2440420d0fd0a7a9a006f2dcb/Artboard_Copy_231.jpg?fit=fill&w=600&h=600", - myAccessRights: { - read: true, - write: true, - delete: true, - }, - accessRights: { - 3: { - read: true, - write: true, - delete: true, - }, - 5: { - read: true, - write: true, - delete: false, - }, - 9: { - read: true, - write: false, - delete: false, - }, - }, - createdAt: "2024-03-04 15:59:51.579217", - lastModified: "2024-03-05 15:18:21.515403", - }, { - workspaceId: 2, - name: "Workspace 2", - description: "Workspace 2 desc", - thumbnail: "", - myAccessRights: { - read: true, - write: true, - delete: false, - }, - accessRights: { - 3: { - read: true, - write: true, - delete: false, - }, - 5: { - read: true, - write: true, - delete: true, - }, - 9: { - read: true, - write: false, - delete: false, - }, - }, - createdAt: "2024-03-05 15:18:21.515403", - lastModified: "2024-04-24 12:03:05.15249", - }, { - workspaceId: 3, - name: "Workspace 3", - description: "Workspace 3 desc", - thumbnail: "https://media.springernature.com/lw703/springer-static/image/art%3A10.1038%2F528452a/MediaObjects/41586_2015_Article_BF528452a_Figg_HTML.jpg", - myAccessRights: { - read: true, - write: false, - delete: false, - }, - accessRights: { - 3: { - read: true, - write: false, - delete: false, - }, - 5: { - read: true, - write: true, - delete: false, - }, - 9: { - read: true, - write: true, - delete: true, - }, - }, - createdAt: "2024-04-24 12:03:05.15249", - lastModified: "2024-06-21 13:00:40.33769", - }], - fetchWorkspaces: function() { if (osparc.auth.Data.getInstance().isGuest()) { return new Promise(resolve => { From 9e47f4e7c36bd038f8efce65c669fb5bf558b6b8 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 08:43:43 +0200 Subject: [PATCH 09/58] new empty study --- .../class/osparc/dashboard/StudyBrowser.js | 11 +++++---- .../source/class/osparc/data/model/Study.js | 23 ++++++++----------- .../client/source/class/osparc/study/Utils.js | 4 ++-- 3 files changed, 17 insertions(+), 21 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 e51634b4731..2c0632b7c3e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -983,10 +983,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __newStudyBtnClicked: function(button) { button.setValue(false); - const minStudyData = osparc.data.model.Study.createMyNewStudyObject(); + const minStudyData = osparc.data.model.Study.createNewStudyObject(); const title = osparc.utils.Utils.getUniqueStudyName(minStudyData.name, this._resourcesList); + const workspace = osparc.store.Workspaces.getWorkspaces(this.getCurrentWorkspaceId()); minStudyData["name"] = title; - minStudyData["description"] = ""; + minStudyData["workbench"] = {}; + minStudyData["accessRights"] = workspace.getAccessRights(); + minStudyData["workspaceId"] = workspace.getWorkspaceId(); + minStudyData["folderId"] = this.getCurrentFolderId(); this._showLoadingPage(this.tr("Creating ") + (minStudyData.name || osparc.product.Utils.getStudyAlias())); const params = { data: minStudyData @@ -1028,9 +1032,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __startStudyAfterCreating: function(studyId) { - if (this.getCurrentFolderId()) { - this.__moveStudyToFolder(studyId, this.getCurrentFolderId()); - } const openCB = () => this._hideLoadingPage(); const cancelCB = () => { this._hideLoadingPage(); diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index 8fd40a67a28..034e98ec8e3 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -214,20 +214,15 @@ qx.Class.define("osparc.data.model.Study", { "workbench" ], - createMyNewStudyObject: function() { - let myNewStudyObject = {}; - const props = qx.util.PropertyUtil.getProperties(osparc.data.model.Study); - for (let key in props) { - const prop = props[key]; - if (!prop.nullable) { - if (typeof prop.init === "object") { - myNewStudyObject[key] = osparc.utils.Utils.deepCloneObject(prop.init); - } else { - myNewStudyObject[key] = prop.init; - } - } - } - return myNewStudyObject; + createNewStudyObject: function() { + const newStudyObject = { + "name": "", + "workbench": {}, + "accessRights": {}, + "workspaceId": null, + "folderId": null, + }; + return newStudyObject; }, getProperties: function() { diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index 3855cf01f96..c6e10f096dd 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.study.Utils", { osparc.store.Services.getService(key, version) .then(metadata => { const newUuid = osparc.utils.Utils.uuidV4(); - const minStudyData = osparc.data.model.Study.createMyNewStudyObject(); + const minStudyData = osparc.data.model.Study.createNewStudyObject(); if (newStudyLabel === undefined) { newStudyLabel = metadata["name"]; } @@ -191,7 +191,7 @@ qx.Class.define("osparc.study.Utils", { }); return; } - const minStudyData = osparc.data.model.Study.createMyNewStudyObject(); + const minStudyData = osparc.data.model.Study.createNewStudyObject(); minStudyData["name"] = templateData["name"]; minStudyData["description"] = templateData["description"]; minStudyData["thumbnail"] = templateData["thumbnail"]; From eef0a50df9530da515a9a0fe389679a70571483a Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 08:45:29 +0200 Subject: [PATCH 10/58] comments --- .../static-webserver/client/source/class/osparc/study/Utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index c6e10f096dd..8ca285f0f80 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -110,6 +110,7 @@ qx.Class.define("osparc.study.Utils", { osparc.store.Services.getService(key, version) .then(metadata => { const newUuid = osparc.utils.Utils.uuidV4(); + // Study will be created in the root folder of the personal workspace const minStudyData = osparc.data.model.Study.createNewStudyObject(); if (newStudyLabel === undefined) { newStudyLabel = metadata["name"]; @@ -191,6 +192,7 @@ qx.Class.define("osparc.study.Utils", { }); return; } + // Study will be created in the root folder of the personal workspace const minStudyData = osparc.data.model.Study.createNewStudyObject(); minStudyData["name"] = templateData["name"]; minStudyData["description"] = templateData["description"]; From 1b7742a8f7891936502a614702efa0e70c342d56 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 08:59:56 +0200 Subject: [PATCH 11/58] createMinStudyObject --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 8 ++++---- .../client/source/class/osparc/data/Resources.js | 6 ++++-- .../client/source/class/osparc/data/model/Study.js | 4 +--- .../client/source/class/osparc/study/Utils.js | 4 ++-- 4 files changed, 11 insertions(+), 11 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 2c0632b7c3e..bdc6f44d411 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -578,7 +578,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; - params.url.workspaceIdId = this.getCurrentWorkspaceId(); + params.url.workspaceId = this.getCurrentWorkspaceId(); params.url.folderId = this.getCurrentFolderId(); if (params.url.orderBy) { return osparc.data.Resources.fetch("studies", "getPageSortBy", params, undefined, options); @@ -983,13 +983,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __newStudyBtnClicked: function(button) { button.setValue(false); - const minStudyData = osparc.data.model.Study.createNewStudyObject(); + const minStudyData = osparc.data.model.Study.createMinStudyObject(); const title = osparc.utils.Utils.getUniqueStudyName(minStudyData.name, this._resourcesList); const workspace = osparc.store.Workspaces.getWorkspaces(this.getCurrentWorkspaceId()); minStudyData["name"] = title; minStudyData["workbench"] = {}; - minStudyData["accessRights"] = workspace.getAccessRights(); - minStudyData["workspaceId"] = workspace.getWorkspaceId(); + minStudyData["accessRights"] = workspace ? workspace.getAccessRights() : null; + minStudyData["workspaceId"] = workspace ? workspace.getWorkspaceId() : null; minStudyData["folderId"] = this.getCurrentFolderId(); this._showLoadingPage(this.tr("Creating ") + (minStudyData.name || osparc.product.Utils.getStudyAlias())); const params = { diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 2346cef70cd..b24f3af640f 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -299,7 +299,9 @@ qx.Class.define("osparc.data.Resources", { endpoints: { getPage: { method: "GET", - url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}" + // Matus will fix it + // url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}" + url: statics.API + "/folders?&folder_id={folderId}&offset={offset}&limit={limit}" }, getOne: { method: "GET", @@ -323,7 +325,7 @@ qx.Class.define("osparc.data.Resources", { endpoints: { getPage: { method: "GET", - url: statics.API + "/workspaces?workspace_id={workspaceId}&offset={offset}&limit={limit}" + url: statics.API + "/workspaces?&offset={offset}&limit={limit}" }, getOne: { method: "GET", diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index 034e98ec8e3..c9308ab9007 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -214,13 +214,11 @@ qx.Class.define("osparc.data.model.Study", { "workbench" ], - createNewStudyObject: function() { + createMinStudyObject: function() { const newStudyObject = { "name": "", "workbench": {}, "accessRights": {}, - "workspaceId": null, - "folderId": null, }; return newStudyObject; }, diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index 8ca285f0f80..12992ea0595 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -111,7 +111,7 @@ qx.Class.define("osparc.study.Utils", { .then(metadata => { const newUuid = osparc.utils.Utils.uuidV4(); // Study will be created in the root folder of the personal workspace - const minStudyData = osparc.data.model.Study.createNewStudyObject(); + const minStudyData = osparc.data.model.Study.createMinStudyObject(); if (newStudyLabel === undefined) { newStudyLabel = metadata["name"]; } @@ -193,7 +193,7 @@ qx.Class.define("osparc.study.Utils", { return; } // Study will be created in the root folder of the personal workspace - const minStudyData = osparc.data.model.Study.createNewStudyObject(); + const minStudyData = osparc.data.model.Study.createMinStudyObject(); minStudyData["name"] = templateData["name"]; minStudyData["description"] = templateData["description"]; minStudyData["thumbnail"] = templateData["thumbnail"]; From a09f8ca8c8a89e8952a42b074d7aa754f09a5803 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:03:43 +0200 Subject: [PATCH 12/58] orgsButton --- .../class/osparc/dashboard/StudyBrowser.js | 5 +-- .../source/class/osparc/data/model/Study.js | 1 - .../organizations/OrganizationsList.js | 2 +- .../osparc/share/NewCollaboratorsManager.js | 39 ++++++++++++++++++- .../client/source/class/osparc/study/Utils.js | 2 +- 5 files changed, 40 insertions(+), 9 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 bdc6f44d411..a7f8867d63c 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -985,11 +985,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { button.setValue(false); const minStudyData = osparc.data.model.Study.createMinStudyObject(); const title = osparc.utils.Utils.getUniqueStudyName(minStudyData.name, this._resourcesList); - const workspace = osparc.store.Workspaces.getWorkspaces(this.getCurrentWorkspaceId()); minStudyData["name"] = title; - minStudyData["workbench"] = {}; - minStudyData["accessRights"] = workspace ? workspace.getAccessRights() : null; - minStudyData["workspaceId"] = workspace ? workspace.getWorkspaceId() : null; + minStudyData["workspaceId"] = this.getCurrentWorkspaceId(); minStudyData["folderId"] = this.getCurrentFolderId(); this._showLoadingPage(this.tr("Creating ") + (minStudyData.name || osparc.product.Utils.getStudyAlias())); const params = { diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index c9308ab9007..cada618f83a 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -218,7 +218,6 @@ qx.Class.define("osparc.data.model.Study", { const newStudyObject = { "name": "", "workbench": {}, - "accessRights": {}, }; return newStudyObject; }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js b/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js index 611f1bd77bc..935c9cfc35b 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js +++ b/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js @@ -25,7 +25,7 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { const studiesLabel = osparc.product.Utils.getStudyAlias({plural: true}); const msg = this.tr("\ - An organization is a group of users who can share ") + studiesLabel + this.tr(".
\ + An organization is a group of users who can share ") + studiesLabel + this.tr(" and other resources.
\ Here you can see the list of organizations you belong to, create new organizations, \ or manage the membership by setting up the access rights of each member in the organization \ if you are a manager or administrator."); diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index b8c8752c6eb..464f97b0127 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -9,6 +9,8 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { extend: osparc.ui.window.SingletonWindow, construct: function(resourceData, showOrganizations = true) { + // OM remove this + showOrganizations = false; this.base(arguments, "collaboratorsManager", this.tr("Share with")); this.set({ layout: new qx.ui.layout.VBox(), @@ -43,7 +45,10 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { members: { __resourceData: null, __showOrganizations: null, + __introLabel: null, + __textFilter: null, __collabButtonsContainer: null, + __orgsButton: null, __shareButton: null, __selectedCollaborators: null, __visibleCollaborators: null, @@ -53,7 +58,14 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { }, __renderLayout: function() { - const filter = new osparc.filter.TextFilter("name", "collaboratorsManager").set({ + const introText = this.tr("In order to start sharing, you first need to belong to an organization."); + const introLabel = this.__introLabel = new qx.ui.basic.Label(introText).set({ + visibility: "excluded", + padding: 8 + }); + this.add(introLabel); + + const filter = this.__textFilter = new osparc.filter.TextFilter("name", "collaboratorsManager").set({ allowStretchX: true, margin: [0, 10, 5, 10] }); @@ -70,6 +82,11 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { const buttons = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ alignX: "right" })); + const orgsButton = this.__orgsButton = new qx.ui.form.Button(this.tr("Check Organizations...")).set({ + appearance: "form-button" + }); + orgsButton.addListener("execute", () => osparc.desktop.organizations.OrganizationsWindow.openWindow(), this); + buttons.add(orgsButton); const shareButton = this.__shareButton = new osparc.ui.form.FetchButton(this.tr("Share")).set({ appearance: "form-button" }); @@ -94,8 +111,23 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { } osparc.store.Store.getInstance().getPotentialCollaborators(false, includeProductEveryone) .then(potentialCollaborators => { + // OM remove this + potentialCollaborators = {}; this.__visibleCollaborators = potentialCollaborators; - this.__addEditors(); + const anyCollaborator = Object.keys(potentialCollaborators).length; + if (anyCollaborator) { + this.__introText.setVisibility(anyCollaborator ? "excluded" : "visible"); + this.__orgsButton.setVisibility(anyCollaborator ? "excluded" : "visible"); + this.__textFilter.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__shareButton.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__addEditors(); + } else { + // tell the user that belonging to an organization is required to start sharing + this.__introText.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__orgsButton.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__textFilter.setVisibility(anyCollaborator ? "excluded" : "visible"); + this.__shareButton.setVisibility(anyCollaborator ? "excluded" : "visible"); + } }); }, @@ -155,6 +187,9 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { }); }, + __openOrganization: function() { + }, + __shareClicked: function() { this.__collabButtonsContainer.setEnabled(false); this.__shareButton.setFetching(true); diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index 12992ea0595..ec720308f55 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.study.Utils", { osparc.store.Services.getService(key, version) .then(metadata => { const newUuid = osparc.utils.Utils.uuidV4(); - // Study will be created in the root folder of the personal workspace + // Study will be created in the root folder of my personal workspace const minStudyData = osparc.data.model.Study.createMinStudyObject(); if (newStudyLabel === undefined) { newStudyLabel = metadata["name"]; From f4f6862c8e91ad9db1f54c455ca4066af0296bfb Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 10:13:53 +0200 Subject: [PATCH 13/58] improvements --- .env-devel | 2 +- .../api_schemas_webserver/folders_v2.py | 2 + .../api/v0/openapi.yaml | 5 +- .../folders/_folders_api.py | 21 ++- .../projects/_crud_api_create.py | 23 +-- .../02/test_projects_crud_handlers__clone.py | 39 ----- ...handlers__clone_in_workspace_and_folder.py | 141 ++++++++++++++++++ 7 files changed, 177 insertions(+), 56 deletions(-) create mode 100644 services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone_in_workspace_and_folder.py diff --git a/.env-devel b/.env-devel index 3f81b81ca52..22690e81871 100644 --- a/.env-devel +++ b/.env-devel @@ -32,7 +32,7 @@ AUTOSCALING_NODES_MONITORING=null AUTOSCALING_POLL_INTERVAL=10 AUTOSCALING_SSM_ACCESS=null -AWS_S3_CLI_S3='{"S3_ACCESS_KEY":"12345678", "S3_BUCKET_NAME":"simcore", "S3_ENDPOINT": "http://172.17.0.1:9001", "S3_SECRET_KEY": "12345678", "S3_REGION": "us-east-1"}' +AWS_S3_CLI_S3=null CATALOG_BACKGROUND_TASK_REST_TIME=60 CATALOG_DEV_FEATURES_ENABLED=0 diff --git a/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py b/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py index 3e0aaf0e05d..e2d4918c435 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import NamedTuple +from models_library.access_rights import AccessRights from models_library.basic_types import IDStr from models_library.folders import FolderID from models_library.users import GroupID @@ -19,6 +20,7 @@ class FolderGet(OutputSchema): modified_at: datetime owner: GroupID workspace_id: WorkspaceID | None + my_access_rights: AccessRights class FolderGetPage(NamedTuple): 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 721983bb231..b948afdee14 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 @@ -3323,7 +3323,7 @@ paths: '403': description: ProjectInvalidRightsError '404': - description: ProjectNotFoundError, UserDefaultWalletNotFoundError + description: UserDefaultWalletNotFoundError, ProjectNotFoundError '409': description: ProjectTooManyProjectOpenedError '422': @@ -8031,6 +8031,7 @@ components: - createdAt - modifiedAt - owner + - myAccessRights type: object properties: folderId: @@ -8064,6 +8065,8 @@ components: exclusiveMinimum: true type: integer minimum: 0 + myAccessRights: + $ref: '#/components/schemas/models_library__access_rights__AccessRights' GenerateInvitation: title: GenerateInvitation required: diff --git a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py index bd8263971e8..98ed5b2f0da 100644 --- a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py +++ b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py @@ -3,6 +3,7 @@ import logging from aiohttp import web +from models_library.access_rights import AccessRights from models_library.api_schemas_webserver.folders_v2 import FolderGet, FolderGetPage from models_library.folders import FolderID from models_library.products import ProductName @@ -35,8 +36,9 @@ async def create_folder( user = await get_user(app, user_id=user_id) workspace_is_private = True + user_folder_access_rights = AccessRights(read=True, write=True, delete=True) if workspace_id: - await check_user_workspace_access( + user_workspace_access_rights = await check_user_workspace_access( app, user_id=user_id, workspace_id=workspace_id, @@ -44,6 +46,7 @@ async def create_folder( permission="write", ) workspace_is_private = False + user_folder_access_rights = user_workspace_access_rights.my_access_rights # Check parent_folder_id lives in the workspace if parent_folder_id: @@ -87,6 +90,7 @@ async def create_folder( modified_at=folder_db.modified, owner=folder_db.created_by_gid, workspace_id=workspace_id, + my_access_rights=user_folder_access_rights, ) @@ -101,8 +105,9 @@ async def get_folder( ) workspace_is_private = True + user_folder_access_rights = AccessRights(read=True, write=True, delete=True) if folder_db.workspace_id: - await check_user_workspace_access( + user_workspace_access_rights = await check_user_workspace_access( app, user_id=user_id, workspace_id=folder_db.workspace_id, @@ -110,6 +115,7 @@ async def get_folder( permission="read", ) workspace_is_private = False + user_folder_access_rights = user_workspace_access_rights.my_access_rights folder_db = await folders_db.get_for_user_or_workspace( app, @@ -126,6 +132,7 @@ async def get_folder( modified_at=folder_db.modified, owner=folder_db.created_by_gid, workspace_id=folder_db.workspace_id, + my_access_rights=user_folder_access_rights, ) @@ -140,9 +147,10 @@ async def list_folders( order_by: OrderBy, ) -> FolderGetPage: workspace_is_private = True + user_folder_access_rights = AccessRights(read=True, write=True, delete=True) if workspace_id: - await check_user_workspace_access( + user_workspace_access_rights = await check_user_workspace_access( app, user_id=user_id, workspace_id=workspace_id, @@ -150,6 +158,7 @@ async def list_folders( permission="read", ) workspace_is_private = False + user_folder_access_rights = user_workspace_access_rights.my_access_rights if folder_id: # Check user access to folder @@ -181,6 +190,7 @@ async def list_folders( modified_at=folder.modified, owner=folder.created_by_gid, workspace_id=folder.workspace_id, + my_access_rights=user_folder_access_rights, ) for folder in folders ], @@ -202,8 +212,9 @@ async def update_folder( ) workspace_is_private = True + user_folder_access_rights = AccessRights(read=True, write=True, delete=True) if folder_db.workspace_id: - await check_user_workspace_access( + user_workspace_access_rights = await check_user_workspace_access( app, user_id=user_id, workspace_id=folder_db.workspace_id, @@ -211,6 +222,7 @@ async def update_folder( permission="write", ) workspace_is_private = False + user_folder_access_rights = user_workspace_access_rights.my_access_rights # Check user has acces to the folder # NOTE: MD: TODO check function! @@ -237,6 +249,7 @@ async def update_folder( modified_at=folder_db.modified, owner=folder_db.created_by_gid, workspace_id=folder_db.workspace_id, + my_access_rights=user_folder_access_rights, ) diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py index 4a14ecd62a3..6eceafb7566 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py @@ -303,17 +303,18 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche if project_node_coro: project_nodes = await project_node_coro - # 1.2 does project belong to some folder? - prj_to_folder_db = await project_to_folders_db.get_project_to_folder( - request.app, - project_id=new_project["uuid"], - private_workspace_user_id_or_none=user_id - if workspace_id is None - else None, - ) - if prj_to_folder_db: - # As user has access to the project, it has implicitly access to the folder - folder_id = prj_to_folder_db.folder_id + # 1.2 does project belong to some folder? + workspace_id = new_project["workspaceId"] + prj_to_folder_db = await project_to_folders_db.get_project_to_folder( + request.app, + project_id=from_study, + private_workspace_user_id_or_none=user_id + if workspace_id is None + else None, + ) + if prj_to_folder_db: + # As user has access to the project, it has implicitly access to the folder + folder_id = prj_to_folder_db.folder_id if predefined_project: # 2. overrides with optional body and re-validate diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py index 2c86badf9fb..be67e5c5bc9 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py @@ -107,42 +107,3 @@ async def test_clone_invalid_project_responds_not_found( await _request_clone_project(client, url) assert err_info.value.status == HTTPNotFound.status_code - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_clone_project_inside_a_workspace_and_folder( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - # mocks backend - storage_subsystem_mock: MockedStorageSubsystem, - mock_catalog_service_api_responses: None, - project_db_cleaner: None, -): - assert client.app - - # 1. create workspace - - # 2. create folder inside a workspace - - # 3. add a project inside the folder - - url = client.app.router["clone_project"].url_for(project_id=project["uuid"]) - assert f"/v0/projects/{project['uuid']}:clone" == url.path - - cloned_project = await _request_clone_project(client, url) - - # check whether it's a clone - assert ProjectID(project["uuid"]) != cloned_project.uuid - assert project["description"] == cloned_project.description - assert parse_obj_as(datetime, project["creationDate"]) < parse_obj_as( - datetime, cloned_project.creation_date - ) - - assert len(project["workbench"]) == len(cloned_project.workbench) - assert set(project["workbench"].keys()) != set( - cloned_project.workbench.keys() - ), "clone does NOT preserve node ids" diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone_in_workspace_and_folder.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone_in_workspace_and_folder.py new file mode 100644 index 00000000000..763976e2504 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone_in_workspace_and_folder.py @@ -0,0 +1,141 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=too-many-arguments +# pylint: disable=unused-argument +# pylint: disable=unused-variable + +from copy import deepcopy +from typing import Any, Iterator + +import pytest +import sqlalchemy as sa +from aiohttp.test_utils import TestClient +from models_library.api_schemas_webserver.projects import ProjectGet +from models_library.folders import FolderID +from models_library.projects import ProjectID +from models_library.workspaces import WorkspaceID +from pytest_simcore.helpers.webserver_login import UserInfoDict +from pytest_simcore.helpers.webserver_parametrizations import MockedStorageSubsystem +from servicelib.aiohttp.long_running_tasks.client import long_running_task_request +from simcore_postgres_database.models.folders import folders +from simcore_postgres_database.models.workspaces import workspaces +from simcore_service_webserver.db.models import UserRole +from simcore_service_webserver.folders._folders_api import create_folder +from simcore_service_webserver.projects._folders_api import move_project_into_folder +from simcore_service_webserver.projects.models import ProjectDict +from simcore_service_webserver.workspaces._workspaces_api import create_workspace +from yarl import URL + + +@pytest.fixture +async def create_workspace_and_folder( + client: TestClient, logged_user: UserInfoDict, postgres_db: sa.engine.Engine +) -> Iterator[tuple[WorkspaceID, FolderID]]: + workspace = await create_workspace( + client.app, + user_id=logged_user["id"], + name="a", + description=None, + thumbnail=None, + product_name="osparc", + ) + + folder = await create_folder( + client.app, + user_id=logged_user["id"], + name="a", + parent_folder_id=None, + product_name="osparc", + workspace_id=workspace.workspace_id, + ) + + yield (workspace.workspace_id, folder.folder_id) + + with postgres_db.connect() as con: + con.execute(folders.delete()) + con.execute(workspaces.delete()) + + +@pytest.fixture +def fake_project( + fake_project: ProjectDict, + workbench_db_column: dict[str, Any], + create_workspace_and_folder: tuple[WorkspaceID, FolderID], +) -> ProjectDict: + # OVERRIDES user_project + project = deepcopy(fake_project) + project["workbench"] = workbench_db_column + project["workspaceId"] = create_workspace_and_folder[0] + return project + + +async def _request_clone_project(client: TestClient, url: URL) -> ProjectGet: + """Raise HTTPError subclasses if request fails""" + # polls until long-running task is done + data = None + async for long_running_task in long_running_task_request( + client.session, url=client.make_url(url.path), json=None, client_timeout=30 + ): + print(f"{long_running_task.progress=}") + if long_running_task.done(): + data = await long_running_task.result() + + assert data is not None + return ProjectGet.parse_obj(data) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_clone_project( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + # mocks backend + storage_subsystem_mock: MockedStorageSubsystem, + mock_catalog_service_api_responses: None, + project_db_cleaner: None, + create_workspace_and_folder: tuple[WorkspaceID, FolderID], +): + assert client.app + + project = user_project + await move_project_into_folder( + client.app, + user_id=logged_user["id"], + project_id=project["uuid"], + folder_id=create_workspace_and_folder[1], + product_name="osparc", + ) + + base_url = client.app.router["list_projects"].url_for() + query_parameters = { + "workspace_id": f"{create_workspace_and_folder[0]}", + "folder_id": f"{create_workspace_and_folder[1]}", + } + url = base_url.with_query(**query_parameters) + resp = await client.get(url) + data = await resp.json() + assert resp.status == 200 + assert len(data["data"]) == 1 + + url = client.app.router["clone_project"].url_for(project_id=project["uuid"]) + assert f"/v0/projects/{project['uuid']}:clone" == url.path + + cloned_project = await _request_clone_project(client, url) + + # check whether it's a clone + assert ProjectID(project["uuid"]) != cloned_project.uuid + assert cloned_project.workspace_id == create_workspace_and_folder[0] + + # check whether it's in right folder + base_url = client.app.router["list_projects"].url_for() + query_parameters = { + "workspace_id": f"{create_workspace_and_folder[0]}", + "folder_id": f"{create_workspace_and_folder[1]}", + } + url = base_url.with_query(**query_parameters) + resp = await client.get(url) + data = await resp.json() + assert resp.status == 200 + assert len(data["data"]) == 2 From 1d9c7f5cab39c68a9f20218d2716e52823c7ea61 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 10:15:53 +0200 Subject: [PATCH 14/58] remove comment --- .../server/src/simcore_service_webserver/folders/_folders_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py index 98ed5b2f0da..44fe103edb7 100644 --- a/services/web/server/src/simcore_service_webserver/folders/_folders_api.py +++ b/services/web/server/src/simcore_service_webserver/folders/_folders_api.py @@ -225,7 +225,6 @@ async def update_folder( user_folder_access_rights = user_workspace_access_rights.my_access_rights # Check user has acces to the folder - # NOTE: MD: TODO check function! await folders_db.get_for_user_or_workspace( app, folder_id=folder_id, From 200be49c9a82a097d94bce1625bbca13bdcd93ac Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:18:32 +0200 Subject: [PATCH 15/58] show email --- .../source/class/osparc/filter/CollaboratorToggleButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js index 90ccbd2171a..216a2ade3fe 100644 --- a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js +++ b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js @@ -36,7 +36,7 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { this.setLabel(label); if ("login" in collaborator) { - this.setToolTipText(collaborator["login"]); + label += ` (${collaborator["login"]})`; } let iconPath = null; From f163f19eab2f71d6d0537675671d8e480d38f380 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:19:20 +0200 Subject: [PATCH 16/58] aesthetics --- .../osparc/share/NewCollaboratorsManager.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 464f97b0127..57b1c9c5129 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -20,7 +20,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { showMaximize: false, autoDestroy: true, modal: true, - width: 262, + width: 300, maxHeight: 500, clickAwayClose: true }); @@ -60,6 +60,8 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { __renderLayout: function() { const introText = this.tr("In order to start sharing, you first need to belong to an organization."); const introLabel = this.__introLabel = new qx.ui.basic.Label(introText).set({ + rich: true, + wrap: true, visibility: "excluded", padding: 8 }); @@ -115,19 +117,15 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { potentialCollaborators = {}; this.__visibleCollaborators = potentialCollaborators; const anyCollaborator = Object.keys(potentialCollaborators).length; - if (anyCollaborator) { - this.__introText.setVisibility(anyCollaborator ? "excluded" : "visible"); - this.__orgsButton.setVisibility(anyCollaborator ? "excluded" : "visible"); - this.__textFilter.setVisibility(anyCollaborator ? "visible" : "excluded"); - this.__shareButton.setVisibility(anyCollaborator ? "visible" : "excluded"); - this.__addEditors(); - } else { - // tell the user that belonging to an organization is required to start sharing - this.__introText.setVisibility(anyCollaborator ? "visible" : "excluded"); - this.__orgsButton.setVisibility(anyCollaborator ? "visible" : "excluded"); - this.__textFilter.setVisibility(anyCollaborator ? "excluded" : "visible"); - this.__shareButton.setVisibility(anyCollaborator ? "excluded" : "visible"); - } + // tell the user that belonging to an organization is required to start sharing + this.__introLabel.setVisibility(anyCollaborator ? "excluded" : "visible"); + this.__orgsButton.setVisibility(anyCollaborator ? "excluded" : "visible"); + + // or start sharing + this.__textFilter.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__visibleCollaborators.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__shareButton.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__addEditors(); }); }, From d66e482844f3e75e883a8fed01cb4c446ef96781 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 10:19:38 +0200 Subject: [PATCH 17/58] add validator --- .../src/models_library/api_schemas_webserver/projects.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 fb43294320d..d87376d8a6d 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 @@ -25,6 +25,7 @@ from ..utils.common_validators import ( empty_str_to_none_pre_validator, none_to_empty_str_pre_validator, + null_or_none_str_to_none_validator, ) from ..utils.pydantic_tools_extension import FieldNotRequired from ._base import EmptyModel, InputSchema, OutputSchema @@ -45,9 +46,13 @@ class ProjectCreateNew(InputSchema): folder_id: FolderID | None = None _empty_is_none = validator( - "uuid", "thumbnail", "description", "workspace_id", allow_reuse=True, pre=True + "uuid", "thumbnail", "description", allow_reuse=True, pre=True )(empty_str_to_none_pre_validator) + _null_or_none_to_none = validator("workspace_id", "folder_id")( + null_or_none_str_to_none_validator + ) + # NOTE: based on OVERRIDABLE_DOCUMENT_KEYS class ProjectCopyOverride(InputSchema): From f7847a8b67a053a94a37b193ddc6219aa1aa4f62 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 10:21:11 +0200 Subject: [PATCH 18/58] remove --- ...t_workspaces__folders_and_projects_crud.py | 60 ------------------- 1 file changed, 60 deletions(-) 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 5c3007977f3..545f28d1b58 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 @@ -225,63 +225,3 @@ async def test_workspaces_full_workflow_with_folders_and_projects( resp = await client.get(url) data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 - - -@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) -async def test_workspaces_full_workflow_with_folders_and_projects2( - client: TestClient, - logged_user: UserInfoDict, - user_project: ProjectDict, - expected: HTTPStatus, - mock_catalog_api_get_services_for_user_in_product: MockerFixture, - fake_project: ProjectDict, - workspaces_clean_db: None, -): - assert client.app - - # create a new workspace - url = client.app.router["create_workspace"].url_for() - resp = await client.post( - url.path, - json={ - "name": "My first workspace", - "description": "Custom description", - "thumbnail": None, - }, - ) - added_workspace, _ = await assert_status(resp, status.HTTP_201_CREATED) - - # Create folder in workspace - url = client.app.router["create_folder"].url_for() - resp = await client.post( - url.path, - json={ - "name": "Original user folder", - "workspaceId": f"{added_workspace['workspaceId']}", - }, - ) - added_folder, _ = await assert_status(resp, status.HTTP_201_CREATED) - - # Create project in workspace and folder - project_data = deepcopy(fake_project) - project_data["workspace_id"] = f"{added_workspace['workspaceId']}" - project_data["folder_id"] = f"{added_folder['folderId']}" - project = await create_project( - client.app, - project_data, - user_id=logged_user["id"], - product_name="osparc", - ) - - # List project in workspace and folder - base_url = client.app.router["list_projects"].url_for() - url = base_url.with_query( - { - "workspace_id": f"{added_workspace['workspaceId']}", - "folder_id": f"{added_folder['folderId']}", - } - ) - resp = await client.get(url) - data, _ = await assert_status(resp, status.HTTP_200_OK) - assert len(data) == 1 - assert data[0]["uuid"] == project["uuid"] From 17ed64b47842f29e3b8338c0cfccd814f57c1160 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:22:06 +0200 Subject: [PATCH 19/58] aesthetics --- .../source/class/osparc/share/NewCollaboratorsManager.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 57b1c9c5129..e245a1c53e0 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -58,7 +58,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { }, __renderLayout: function() { - const introText = this.tr("In order to start sharing, you first need to belong to an organization."); + const introText = this.tr("In order to start Sharing with other members, you first need to belong to an Organization."); const introLabel = this.__introLabel = new qx.ui.basic.Label(introText).set({ rich: true, wrap: true, @@ -85,7 +85,8 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { alignX: "right" })); const orgsButton = this.__orgsButton = new qx.ui.form.Button(this.tr("Check Organizations...")).set({ - appearance: "form-button" + appearance: "form-button", + visibility: "excluded", }); orgsButton.addListener("execute", () => osparc.desktop.organizations.OrganizationsWindow.openWindow(), this); buttons.add(orgsButton); @@ -123,7 +124,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { // or start sharing this.__textFilter.setVisibility(anyCollaborator ? "visible" : "excluded"); - this.__visibleCollaborators.setVisibility(anyCollaborator ? "visible" : "excluded"); + this.__collabButtonsContainer.setVisibility(anyCollaborator ? "visible" : "excluded"); this.__shareButton.setVisibility(anyCollaborator ? "visible" : "excluded"); this.__addEditors(); }); From 2671b9d7ebaeed7ebadbda4d8c0f48b1e5c02e54 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 10:23:35 +0200 Subject: [PATCH 20/58] fix validator --- .../src/models_library/api_schemas_webserver/projects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 d87376d8a6d..b644ac52d0b 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 @@ -49,9 +49,9 @@ class ProjectCreateNew(InputSchema): "uuid", "thumbnail", "description", allow_reuse=True, pre=True )(empty_str_to_none_pre_validator) - _null_or_none_to_none = validator("workspace_id", "folder_id")( - null_or_none_str_to_none_validator - ) + _null_or_none_to_none = validator( + "workspace_id", "folder_id", allow_reuse=True, pre=True + )(null_or_none_str_to_none_validator) # NOTE: based on OVERRIDABLE_DOCUMENT_KEYS From 819575e2cddee52a87c70a5eb2db58b5164647f9 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:31:48 +0200 Subject: [PATCH 21/58] ux --- .../class/osparc/filter/CollaboratorToggleButton.js | 12 +++++++++--- .../class/osparc/share/NewCollaboratorsManager.js | 8 +++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js index 216a2ade3fe..31d3aef3512 100644 --- a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js +++ b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js @@ -26,7 +26,13 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { let label = null; if (collaborator["first_name"]) { // user - label = `${collaborator["first_name"]} ${"last_name" in collaborator && collaborator["last_name"] != null ? collaborator["last_name"] : ""}`; + label = collaborator["first_name"]; + if (collaborator["last_name"]) { + label += ` ${collaborator["last_name"]}`; + } + if (collaborator["login"]) { + label += ` (${collaborator["login"]})`; + } } else if ("login" in collaborator) { label = collaborator["login"]; } else { @@ -35,8 +41,8 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { } this.setLabel(label); - if ("login" in collaborator) { - label += ` (${collaborator["login"]})`; + if (collaborator["login"]) { + this.setToolTipText(collaborator["login"]); } let iconPath = null; diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index e245a1c53e0..9fbed72144e 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -9,8 +9,6 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { extend: osparc.ui.window.SingletonWindow, construct: function(resourceData, showOrganizations = true) { - // OM remove this - showOrganizations = false; this.base(arguments, "collaboratorsManager", this.tr("Share with")); this.set({ layout: new qx.ui.layout.VBox(), @@ -91,7 +89,8 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { orgsButton.addListener("execute", () => osparc.desktop.organizations.OrganizationsWindow.openWindow(), this); buttons.add(orgsButton); const shareButton = this.__shareButton = new osparc.ui.form.FetchButton(this.tr("Share")).set({ - appearance: "form-button" + appearance: "form-button", + enabled: false, }); shareButton.addListener("execute", () => this.__shareClicked(), this); buttons.add(shareButton); @@ -114,8 +113,6 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { } osparc.store.Store.getInstance().getPotentialCollaborators(false, includeProductEveryone) .then(potentialCollaborators => { - // OM remove this - potentialCollaborators = {}; this.__visibleCollaborators = potentialCollaborators; const anyCollaborator = Object.keys(potentialCollaborators).length; // tell the user that belonging to an organization is required to start sharing @@ -139,6 +136,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { } else { this.__selectedCollaborators.remove(collaborator.gid); } + this.__shareButton.setEnabled(Boolean(this.__selectedCollaborators.length)); }, this); collaboratorButton.subscribeToFilterGroup("collaboratorsManager"); return collaboratorButton; From aabf1aa7ff64304b403e9b920753dd4619ea4467 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:32:59 +0200 Subject: [PATCH 22/58] aesthetics --- .../client/source/class/osparc/share/NewCollaboratorsManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 9fbed72144e..148d8df6f14 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -18,7 +18,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { showMaximize: false, autoDestroy: true, modal: true, - width: 300, + width: 350, maxHeight: 500, clickAwayClose: true }); From 48b56477ede8684798b08b6b1cae4d8b84d01a84 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 10:53:45 +0200 Subject: [PATCH 23/58] contextProps --- .../source/class/osparc/dashboard/StudyBrowser.js | 12 ++++++++++-- .../client/source/class/osparc/study/Utils.js | 12 ++++++------ 2 files changed, 16 insertions(+), 8 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 a7f8867d63c..5366be54c7d 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1007,7 +1007,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const title = osparc.utils.Utils.getUniqueStudyName(newStudyName, this._resourcesList); templateCopyData.name = title; this._showLoadingPage(this.tr("Creating ") + (newStudyName || osparc.product.Utils.getStudyAlias())); - osparc.study.Utils.createStudyFromTemplate(templateCopyData, this._loadingPage) + const contextProps = { + workspaceId: this.getCurrentWorkspaceId(), + folderId: this.getCurrentFolderId(), + }; + osparc.study.Utils.createStudyFromTemplate(templateCopyData, this._loadingPage, contextProps) .then(studyId => this.__startStudyAfterCreating(studyId)) .catch(err => { this._hideLoadingPage(); @@ -1019,7 +1023,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __newStudyFromServiceBtnClicked: function(button, key, version, newStudyLabel) { button.setValue(false); this._showLoadingPage(this.tr("Creating ") + osparc.product.Utils.getStudyAlias()); - osparc.study.Utils.createStudyFromService(key, version, this._resourcesList, newStudyLabel) + const contextProps = { + workspaceId: this.getCurrentWorkspaceId(), + folderId: this.getCurrentFolderId(), + }; + osparc.study.Utils.createStudyFromService(key, version, this._resourcesList, newStudyLabel, contextProps) .then(studyId => this.__startStudyAfterCreating(studyId)) .catch(err => { this._hideLoadingPage(); diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index ec720308f55..dbeae76c203 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -105,13 +105,13 @@ qx.Class.define("osparc.study.Utils", { return isRetired; }, - createStudyFromService: function(key, version, existingStudies, newStudyLabel) { + createStudyFromService: function(key, version, existingStudies, newStudyLabel, contextProps = {}) { return new Promise((resolve, reject) => { osparc.store.Services.getService(key, version) .then(metadata => { const newUuid = osparc.utils.Utils.uuidV4(); - // Study will be created in the root folder of my personal workspace - const minStudyData = osparc.data.model.Study.createMinStudyObject(); + // context props, otherwise Study will be created in the root folder of my personal workspace + const minStudyData = Object.assign(osparc.data.model.Study.createMinStudyObject(), contextProps); if (newStudyLabel === undefined) { newStudyLabel = metadata["name"]; } @@ -182,7 +182,7 @@ qx.Class.define("osparc.study.Utils", { }); }, - createStudyFromTemplate: function(templateData, loadingPage) { + createStudyFromTemplate: function(templateData, loadingPage, contextProps = {}) { return new Promise((resolve, reject) => { const inaccessibleServices = this.getInaccessibleServices(templateData["workbench"]); if (inaccessibleServices.length) { @@ -192,8 +192,8 @@ qx.Class.define("osparc.study.Utils", { }); return; } - // Study will be created in the root folder of the personal workspace - const minStudyData = osparc.data.model.Study.createMinStudyObject(); + // context props, otherwise Study will be created in the root folder of my personal workspace + const minStudyData = Object.assign(osparc.data.model.Study.createMinStudyObject(), contextProps); minStudyData["name"] = templateData["name"]; minStudyData["description"] = templateData["description"]; minStudyData["thumbnail"] = templateData["thumbnail"]; From 7b5d976ab2d62deb662cfb2ad0d3d63bea2d559e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 11:10:45 +0200 Subject: [PATCH 24/58] review @sanderegg --- .../02/test_projects_crud_handlers__clone.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py index be67e5c5bc9..3966ba4c710 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py @@ -16,7 +16,11 @@ from models_library.projects import ProjectID from pydantic import parse_obj_as from pytest_simcore.helpers.webserver_login import UserInfoDict -from pytest_simcore.helpers.webserver_parametrizations import MockedStorageSubsystem +from pytest_simcore.helpers.webserver_parametrizations import ( + MockedStorageSubsystem, + standard_role_response, +) +from servicelib.aiohttp import status from servicelib.aiohttp.long_running_tasks.client import long_running_task_request from simcore_service_webserver.db.models import UserRole from simcore_service_webserver.projects.models import ProjectDict @@ -48,6 +52,34 @@ async def _request_clone_project(client: TestClient, url: URL) -> ProjectGet: return ProjectGet.parse_obj(data) +@pytest.mark.parametrize(*standard_role_response(), ids=str) +async def test_clone_project_user_permissions( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + # mocks backend + storage_subsystem_mock: MockedStorageSubsystem, + mock_catalog_service_api_responses: None, + project_db_cleaner: None, + expected, +): + assert client.app + + project = user_project + + url = client.app.router["clone_project"].url_for(project_id=project["uuid"]) + assert f"/v0/projects/{project['uuid']}:clone" == url.path + + try: + cloned_project = await _request_clone_project(client, url) + except Exception as exc: + assert exc.status == expected.ok + + if expected.ok == status.HTTP_200_OK: + # check whether it's a clone + assert ProjectID(project["uuid"]) != cloned_project.uuid + + @pytest.mark.parametrize( "user_role", [UserRole.USER], From 11a521b573f36e4056afb46c44c818a6858ab4ff Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 11:23:39 +0200 Subject: [PATCH 25/58] reloadWorkspaceButtons --- .../class/osparc/dashboard/ResourceFilter.js | 31 +++++++++++++------ .../class/osparc/dashboard/StudyBrowser.js | 10 ++++-- .../source/class/osparc/store/Workspaces.js | 16 +++++----- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js index 0a47f4e12ec..5e3023b13f0 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js @@ -43,6 +43,8 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { members: { __resourceType: null, + __contextLayout: null, + __contextRadioGroup: null, __sharedWithButtons: null, __workspaceButtons: null, __tagButtons: null, @@ -70,9 +72,9 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { /* SHARED WITH */ __createSharedWithFilterLayout: function() { - const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); + const layout = this.__contextLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); - const radioGroup = new qx.ui.form.RadioGroup(); + const radioGroup = this.__contextRadioGroup = new qx.ui.form.RadioGroup(); radioGroup.setAllowEmptySelection(false); const options = osparc.dashboard.SearchBarFilter.getSharedWithOptions(this.__resourceType); @@ -112,7 +114,7 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { }); if (this.__resourceType === "study") { - this.__addWorkspaceButtons(layout, radioGroup); + this.__addWorkspaceButtons(); } return layout; @@ -120,19 +122,30 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { /* /SHARED WITH */ /* WORKSPACES */ - __addWorkspaceButtons: function(layout, radioGroup) { - layout.add(new qx.ui.core.Spacer()); + __addWorkspaceButtons: function() { + this.__contextLayout.add(new qx.ui.core.Spacer()); const workspacesButton = new qx.ui.toolbar.RadioButton(this.tr("Shared Workspaces"), osparc.store.Workspaces.iconPath(22)); workspacesButton.workspaceId = -1; workspacesButton.set({ appearance: "filter-toggle-button" }); - layout.add(workspacesButton); - radioGroup.add(workspacesButton); + this.__contextLayout.add(workspacesButton); + this.__contextRadioGroup.add(workspacesButton); workspacesButton.addListener("execute", () => { this.fireDataEvent("changeWorkspace", workspacesButton.workspaceId); }); + this.reloadWorkspaceButtons(); + }, + + reloadWorkspaceButtons: function() { + // remove first the workspaces + for (let i=this.__workspaceButtons.length-1; i >= 0; i--) { + const workspaceButton = this.__workspaceButtons[i]; + this.__contextLayout.remove(workspaceButton); + this.__contextRadioGroup.remove(workspaceButton); + this.__workspaceButtons = []; + } osparc.store.Workspaces.fetchWorkspaces() .then(workspaces => { workspaces.forEach(workspace => { @@ -143,8 +156,8 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { appearance: "filter-toggle-button", marginLeft: 15, }); - layout.add(workspaceButton); - radioGroup.add(workspaceButton); + this.__contextLayout.add(workspaceButton); + this.__contextRadioGroup.add(workspaceButton); workspaceButton.addListener("execute", () => { this.fireDataEvent("changeWorkspace", workspaceButton.workspaceId); }, this); 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 5366be54c7d..6efeb9028d0 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -412,8 +412,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const newWorkspaceCard = new osparc.dashboard.WorkspaceButtonNew(); newWorkspaceCard.setCardKey("new-workspace"); newWorkspaceCard.subscribeToFilterGroup("searchBarFilter"); - newWorkspaceCard.addListener("createWorkspace", () => this.__reloadWorkspaces()); - newWorkspaceCard.addListener("updateWorkspace", () => this.__reloadWorkspaces()); + [ + "createWorkspace", + "updateWorkspace" + ].forEach(e => { + newWorkspaceCard.addListener(e, () => { + this.__reloadWorkspaces(); + }); + }); this._resourcesContainer.addNewWorkspaceCard(newWorkspaceCard); }, diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 781594612be..794a5d0f456 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -29,6 +29,14 @@ qx.Class.define("osparc.store.Workspaces", { return source+iconsSize; }, + createNewWorkspaceData: function(name, description = "", thumbnail = "") { + return { + name, + description, + thumbnail, + }; + }, + fetchWorkspaces: function() { if (osparc.auth.Data.getInstance().isGuest()) { return new Promise(resolve => { @@ -46,14 +54,6 @@ qx.Class.define("osparc.store.Workspaces", { }); }, - createNewWorkspaceData: function(name, description = "", thumbnail = "") { - return { - name, - description, - thumbnail, - }; - }, - postWorkspace: function(newWorkspaceData) { const params = { data: newWorkspaceData From 750a15b8122091aad96e5f51b831307db7a8256f Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 11:28:41 +0200 Subject: [PATCH 26/58] modifiedAt --- .../source/class/osparc/dashboard/ResourceBrowserBase.js | 5 +++-- .../client/source/class/osparc/dashboard/StudyBrowser.js | 1 + .../source/class/osparc/dashboard/WorkspaceButtonItem.js | 2 +- .../client/source/class/osparc/data/model/Workspace.js | 6 +++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js index 40178ea468d..e36c379ceb7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -186,6 +186,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { members: { __leftFilters: null, + _resourceFilter: null, __centerLayout: null, _resourceType: null, _resourcesList: null, @@ -256,7 +257,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("workspaceSelected", e => { const workspaceId = e.getData(); this._workspaceSelected(workspaceId); - this.__resourceFilter.workspaceSelected(workspaceId); + this._resourceFilter.workspaceSelected(workspaceId); }); resourcesContainer.addListener("workspaceUpdated", e => this._workspaceUpdated(e.getData())); resourcesContainer.addListener("deleteWorkspaceRequested", e => this._deleteWorkspaceRequested(e.getData())); @@ -355,7 +356,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { }, _addResourceFilter: function() { - const resourceFilter = this.__resourceFilter = new osparc.dashboard.ResourceFilter(this._resourceType).set({ + const resourceFilter = this._resourceFilter = new osparc.dashboard.ResourceFilter(this._resourceType).set({ marginTop: osparc.dashboard.SearchBarFilter.HEIGHT + 10, // aligned with toolbar buttons: search bar + spacing maxWidth: this.self().SIDE_SPACER_WIDTH, width: this.self().SIDE_SPACER_WIDTH 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 6efeb9028d0..49eda13e9be 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -417,6 +417,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "updateWorkspace" ].forEach(e => { newWorkspaceCard.addListener(e, () => { + this._resourceFilter.reloadWorkspaces(); this.__reloadWorkspaces(); }); }); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index 3c00d2600a2..fe6794402fb 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -157,7 +157,7 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { converter: thumbnail => thumbnail ? thumbnail : osparc.store.Workspaces.iconPath(-1) }); workspace.bind("accessRights", this, "accessRights"); - workspace.bind("lastModified", this, "lastModified"); + workspace.bind("modifiedAt", this, "modifiedAt"); workspace.bind("myAccessRights", this, "myAccessRights"); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js index bdcef9ba779..56023d1eb4e 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js @@ -36,7 +36,7 @@ qx.Class.define("osparc.data.model.Workspace", { myAccessRights: workspaceData.myAccessRights, accessRights: workspaceData.accessRights, createdAt: new Date(workspaceData.createdAt), - lastModified: new Date(workspaceData.lastModified), + modifiedAt: new Date(workspaceData.modifiedAt), }); }, @@ -90,11 +90,11 @@ qx.Class.define("osparc.data.model.Workspace", { event: "changeCreatedAt" }, - lastModified: { + modifiedAt: { check: "Date", nullable: true, init: null, - event: "changeLastModified" + event: "changeModifiedAt" } }, From cd2a4b62f662d53f474948be7258c94f665bb980 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 11:30:25 +0200 Subject: [PATCH 27/58] after review -> addding overwrite also for other ProjectGet --- .../projects/_access_rights_api.py | 4 ++-- .../projects/_crud_api_create.py | 22 ++++++++++++++++--- .../projects/projects_api.py | 15 ++++++++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/_access_rights_api.py b/services/web/server/src/simcore_service_webserver/projects/_access_rights_api.py index c306e94f4e0..1c7919a4985 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_access_rights_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_access_rights_api.py @@ -35,10 +35,10 @@ async def get_user_project_access_rights( """ This function resolves user access rights on the project resource. - If project belong to user private workspace (workspace_id = None) then it is resolved + If project belongs to user private workspace (workspace_id = None) then it is resolved via user <--> groups <--> projects_to_groups. - If project belonsg to shared workspace (workspace_id not None) then it is resolved + If project belongs to shared workspace (workspace_id not None) then it is resolved via user <--> groups <--> workspace_access_rights """ db: ProjectDBAPI = app[APP_PROJECT_DBAPI] diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py index 6eceafb7566..d980c72fa3d 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py @@ -14,6 +14,7 @@ from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from models_library.utils.json_serialization import json_dumps +from models_library.workspaces import UserWorkspaceAccessRightsDB from pydantic import parse_obj_as from servicelib.aiohttp.long_running_tasks.server import TaskProgress from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON @@ -32,6 +33,7 @@ get_project_total_size_simcore_s3, ) from ..users.api import get_user_fullname +from ..workspaces import _workspaces_db as workspaces_db from ..workspaces.api import check_user_workspace_access from ..workspaces.errors import WorkspaceAccessForbiddenError from . import _folders_db as project_to_folders_db @@ -308,9 +310,9 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche prj_to_folder_db = await project_to_folders_db.get_project_to_folder( request.app, project_id=from_study, - private_workspace_user_id_or_none=user_id - if workspace_id is None - else None, + private_workspace_user_id_or_none=( + user_id if workspace_id is None else None + ), ) if prj_to_folder_db: # As user has access to the project, it has implicitly access to the folder @@ -388,6 +390,20 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche # Adds permalink await update_or_pop_permalink_in_project(request, new_project) + # Overwrite project access rights + if workspace_id: + workspace_db: UserWorkspaceAccessRightsDB = ( + await workspaces_db.get_workspace_for_user( + app=request.app, + user_id=user_id, + workspace_id=workspace_id, + product_name=product_name, + ) + ) + new_project["accessRights"] = { + key: access.dict() for key, access in workspace_db.access_rights.items() + } + # Ensures is like ProjectGet data = ProjectGet.parse_obj(new_project).data(exclude_unset=True) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 18cac2b32f9..4761cd1a1bd 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -62,6 +62,7 @@ from models_library.utils.fastapi_encoders import jsonable_encoder from models_library.utils.json_serialization import json_dumps from models_library.wallets import ZERO_CREDITS, WalletID, WalletInfo +from models_library.workspaces import UserWorkspaceAccessRightsDB from pydantic import ByteSize, parse_obj_as from servicelib.aiohttp.application_keys import APP_FIRE_AND_FORGET_TASKS_KEY from servicelib.common_headers import ( @@ -118,6 +119,7 @@ ) from ..wallets import api as wallets_api from ..wallets.errors import WalletNotEnoughCreditsError +from ..workspaces import _workspaces_db as workspaces_db from . import _crud_api_delete, _nodes_api from ._access_rights_api import ( check_user_project_permission, @@ -196,7 +198,18 @@ async def get_project_for_user( user_id, project, project_type is ProjectType.TEMPLATE, app ) - # If from workspace -> hack workspace permissions + if project["workspaceId"] is not None: + workspace_db: UserWorkspaceAccessRightsDB = ( + await workspaces_db.get_workspace_for_user( + app=app, + user_id=user_id, + workspace_id=project["workspaceId"], + product_name=product_name, + ) + ) + project["accessRights"] = { + key: access.dict() for key, access in workspace_db.access_rights.items() + } Project.parse_obj(project) # NOTE: only validates return project From df474e5feaf9cc88222edbb83e9e2cec300ad361 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 11:34:34 +0200 Subject: [PATCH 28/58] minor --- .../source/class/osparc/dashboard/WorkspaceButtonItem.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index fe6794402fb..cd5fa3be2a6 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -85,10 +85,10 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { apply: "__applyAccessRights" }, - lastModified: { + modifiedAt: { check: "Date", nullable: true, - apply: "__applyLastModified" + apply: "__applyModifiedAt" } }, @@ -234,7 +234,7 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { } }, - __applyLastModified: function(value) { + __applyModifiedAt: function(value) { const label = this.getChildControl("modified-text"); label.setValue(osparc.utils.Utils.formatDateAndTime(value)); }, From 19083ecb65e010c5f602831cf772489ea515e0d9 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 11:45:38 +0200 Subject: [PATCH 29/58] improvements --- .../projects/_tags_api.py | 30 +++++++++++++++++++ .../02/test_projects_crud_handlers__clone.py | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/_tags_api.py b/services/web/server/src/simcore_service_webserver/projects/_tags_api.py index 60f08dd1387..d64a2840a06 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_tags_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_tags_api.py @@ -7,7 +7,9 @@ from aiohttp import web from models_library.projects import ProjectID from models_library.users import UserID +from models_library.workspaces import UserWorkspaceAccessRightsDB +from ..workspaces import _workspaces_db as workspaces_db from ._access_rights_api import check_user_project_permission from .db import ProjectDBAPI from .models import ProjectDict @@ -32,6 +34,20 @@ async def add_tag( project: ProjectDict = await db.add_tag( project_uuid=f"{project_uuid}", user_id=user_id, tag_id=int(tag_id) ) + + if project["workspaceId"] is not None: + workspace_db: UserWorkspaceAccessRightsDB = ( + await workspaces_db.get_workspace_for_user( + app=app, + user_id=user_id, + workspace_id=project["workspaceId"], + product_name=product_name, + ) + ) + project["accessRights"] = { + key: access.dict() for key, access in workspace_db.access_rights.items() + } + return project @@ -52,4 +68,18 @@ async def remove_tag( project: ProjectDict = await db.remove_tag( project_uuid=f"{project_uuid}", user_id=user_id, tag_id=tag_id ) + + if project["workspaceId"] is not None: + workspace_db: UserWorkspaceAccessRightsDB = ( + await workspaces_db.get_workspace_for_user( + app=app, + user_id=user_id, + workspace_id=project["workspaceId"], + product_name=product_name, + ) + ) + project["accessRights"] = { + key: access.dict() for key, access in workspace_db.access_rights.items() + } + return project diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py index 3966ba4c710..6ca7392dd4b 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__clone.py @@ -72,8 +72,8 @@ async def test_clone_project_user_permissions( try: cloned_project = await _request_clone_project(client, url) - except Exception as exc: - assert exc.status == expected.ok + except Exception as exc: # pylint: disable=broad-exception-caught + assert exc.status == expected.ok # pylint: disable=no-member if expected.ok == status.HTTP_200_OK: # check whether it's a clone From 4fea7953f66e8667ed24e3bb5d46db9da377246d Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 11:49:45 +0200 Subject: [PATCH 30/58] improve Quick Start logic --- .../class/osparc/dashboard/StudyBrowser.js | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 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 49eda13e9be..859eb13cf97 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -183,62 +183,57 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (this._loadingResourcesBtn.isFetching()) { return; } + osparc.data.Resources.get("tasks") .then(tasks => { if (tasks && tasks.length) { this.__tasksReceived(tasks); } }); + + // Show "Contact Us" message if services.length === 0 + // Most probably is a product-stranger user (it can also be that the catalog is down) + osparc.store.Services.getServicesLatest() + .then(services => { + if (Object.keys(services).length === 0) { + const noAccessText = new qx.ui.basic.Label().set({ + selectable: true, + rich: true, + font: "text-18", + paddingTop: 20 + }); + let msg = this.tr("It seems you don't have access to this product."); + msg += "
"; + msg += "
"; + msg += this.tr("Please contact us:"); + msg += "
"; + const supportEmail = osparc.store.VendorInfo.getInstance().getSupportEmail(); + noAccessText.setValue(msg + supportEmail); + this._addToLayout(noAccessText); + } + }); + this._loadingResourcesBtn.setFetching(true); this._loadingResourcesBtn.setVisibility("visible"); - const request = this.__getNextRequest(); - request + this.__getNextStudiesRequest() .then(resp => { const studies = resp["data"]; this._resourcesContainer.getFlatList().nextRequest = resp["_links"]["next"]; this.__addStudiesToList(studies); - // Show "Contact Us" message if studies.length === 0 && templates.length === 0 && services.length === 0 - // Most probably is a product-stranger user (it can also be that the catalog is down) - const nStudies = "_meta" in resp ? resp["_meta"]["total"] : 0; - if (nStudies === 0) { - const promises = [ - osparc.store.Store.getInstance().getTemplates(), - osparc.store.Services.getServicesLatest(), - ]; - if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { - promises.push(osparc.store.Folders.getInstance().fetchFolders()); - } - Promise.all(promises).then(values => { - const templates = values[0]; - const services = values[1]; - if (templates.length === 0 && Object.keys(services).length === 0) { - const noAccessText = new qx.ui.basic.Label().set({ - selectable: true, - rich: true, - font: "text-18", - paddingTop: 20 - }); - let msg = this.tr("It seems you don't have access to this product."); - msg += "
"; - msg += "
"; - msg += this.tr("Please contact us:"); - msg += "
"; - const supportEmail = osparc.store.VendorInfo.getInstance().getSupportEmail(); - noAccessText.setValue(msg + supportEmail); - this._addAt(noAccessText, 2); - } - }); - } - - // Show Quick Start if studies.length === 0 + // Show Quick Start if there are no studies in the root folder of the personal workspace const quickStart = osparc.product.quickStart.Utils.getQuickStart(); if (quickStart) { const dontShow = osparc.utils.Utils.localCache.getLocalStorageItem(quickStart.localStorageStr); if (dontShow === "true") { return; } - if (nStudies === 0) { + const nStudies = "_meta" in resp ? resp["_meta"]["total"] : 0; + if ( + nStudies === 0 && + this.getWorkspaceId() === null && + this.getCurrentFolderId() === null + ) { const tutorialWindow = quickStart.tutorial(); tutorialWindow.center(); tutorialWindow.open(); @@ -568,7 +563,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return null; }, - __getNextRequest: function() { + __getNextStudiesRequest: function() { const params = { url: { offset: 0, From 70e10039dbfe408a48d963152b24fc1fbe291275 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 11:54:01 +0200 Subject: [PATCH 31/58] __currentRequest --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 859eb13cf97..090abc31e1e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -102,6 +102,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, members: { + __currentRequest: null, __workspacesList: null, __foldersList: null, @@ -215,7 +216,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._loadingResourcesBtn.setFetching(true); this._loadingResourcesBtn.setVisibility("visible"); - this.__getNextStudiesRequest() + const currentRequest = this.__currentRequest = this.__getNextStudiesRequest(); + currentRequest .then(resp => { const studies = resp["data"]; this._resourcesContainer.getFlatList().nextRequest = resp["_links"]["next"]; @@ -242,6 +244,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }) .catch(err => console.error(err)) .finally(() => { + this.__currentRequest = null; this._loadingResourcesBtn.setFetching(false); this._loadingResourcesBtn.setVisibility(this._resourcesContainer.getFlatList().nextRequest === null ? "excluded" : "visible"); this._moreResourcesRequired(); @@ -412,7 +415,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "updateWorkspace" ].forEach(e => { newWorkspaceCard.addListener(e, () => { - this._resourceFilter.reloadWorkspaces(); + this._resourceFilter.reloadWorkspaceButtons(); this.__reloadWorkspaces(); }); }); From 022cfff2ea816e8183bc6d98970fb37ada73c00b Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Thu, 5 Sep 2024 12:37:48 +0200 Subject: [PATCH 32/58] review @prespo --- .../simcore_service_webserver/projects/_crud_api_create.py | 2 +- .../src/simcore_service_webserver/projects/_crud_api_read.py | 2 +- .../src/simcore_service_webserver/projects/_tags_api.py | 4 ++-- .../src/simcore_service_webserver/projects/projects_api.py | 2 +- .../web/server/tests/unit/with_dbs/03/folders/test_folders.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py index d980c72fa3d..1cb21629baa 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_create.py @@ -401,7 +401,7 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche ) ) new_project["accessRights"] = { - key: access.dict() for key, access in workspace_db.access_rights.items() + gid: access.dict() for gid, access in workspace_db.access_rights.items() } # Ensures is like ProjectGet diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py index bb6850e0402..7efd1dd600c 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py @@ -54,7 +54,7 @@ async def _append_fields( # replace project access rights (if project is in workspace) if workspace_access_rights: project["accessRights"] = { - key: access.dict() for key, access in workspace_access_rights.items() + gid: access.dict() for gid, access in workspace_access_rights.items() } # validate diff --git a/services/web/server/src/simcore_service_webserver/projects/_tags_api.py b/services/web/server/src/simcore_service_webserver/projects/_tags_api.py index d64a2840a06..ba4be3c5fb4 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_tags_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_tags_api.py @@ -45,7 +45,7 @@ async def add_tag( ) ) project["accessRights"] = { - key: access.dict() for key, access in workspace_db.access_rights.items() + gid: access.dict() for gid, access in workspace_db.access_rights.items() } return project @@ -79,7 +79,7 @@ async def remove_tag( ) ) project["accessRights"] = { - key: access.dict() for key, access in workspace_db.access_rights.items() + gid: access.dict() for gid, access in workspace_db.access_rights.items() } return project diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 4761cd1a1bd..fc130436caf 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -208,7 +208,7 @@ async def get_project_for_user( ) ) project["accessRights"] = { - key: access.dict() for key, access in workspace_db.access_rights.items() + gid: access.dict() for gid, access in workspace_db.access_rights.items() } Project.parse_obj(project) # NOTE: only validates 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 8d84fcec39d..16cc4f667fb 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 @@ -74,7 +74,7 @@ async def test_folders_full_workflow( ) resp = await client.get(url) data, _ = await assert_status(resp, status.HTTP_200_OK) - assert isinstance(data, FolderGet) + assert FolderGet.parse_obj(data) assert data["folderId"] == added_folder["folderId"] assert data["name"] == "My first folder" From 6ec3a9b72ba406d3f6d8bae56320d04a81548663 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 13:00:52 +0200 Subject: [PATCH 33/58] comment --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 3 +++ 1 file changed, 3 insertions(+) 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 090abc31e1e..4e67243e142 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -216,6 +216,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._loadingResourcesBtn.setFetching(true); this._loadingResourcesBtn.setVisibility("visible"); + if (this.__currentRequest) { + // cancel currentRequest + } const currentRequest = this.__currentRequest = this.__getNextStudiesRequest(); currentRequest .then(resp => { From f64e7930aab6cd18dc38e4c79ae9a72e9e37da32 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 13:03:37 +0200 Subject: [PATCH 34/58] fixed --- .../client/source/class/osparc/data/Resources.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index b24f3af640f..8713036afb0 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -299,9 +299,7 @@ qx.Class.define("osparc.data.Resources", { endpoints: { getPage: { method: "GET", - // Matus will fix it - // url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}" - url: statics.API + "/folders?&folder_id={folderId}&offset={offset}&limit={limit}" + url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}" }, getOne: { method: "GET", From 07f02be0b81e7a8fe90af38b25bb428a1334a8c3 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 13:06:44 +0200 Subject: [PATCH 35/58] Do not show Share menu button --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4e67243e142..7d83698efb1 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1122,7 +1122,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { menu.addSeparator(); - if (writeAccess) { + if (writeAccess && this.getCurrentWorkspaceId() === null) { const shareButton = this._getShareMenuButton(card); if (shareButton) { menu.add(shareButton); From 35e6e0f9453f55e9e9db3d9acbdd15f2f9239823 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 13:13:16 +0200 Subject: [PATCH 36/58] canIChangePermissions --- .../client/source/class/osparc/share/Collaborators.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/share/Collaborators.js b/services/static-webserver/client/source/class/osparc/share/Collaborators.js index c315c7d26d3..9c282f858ed 100644 --- a/services/static-webserver/client/source/class/osparc/share/Collaborators.js +++ b/services/static-webserver/client/source/class/osparc/share/Collaborators.js @@ -189,7 +189,11 @@ qx.Class.define("osparc.share.Collaborators", { return control || this.base(arguments, id); }, - __amIOwner: function() { + __canIChangePermissions: function() { + if (this._resourceType === "study" && this._serializedDataCopy["workspaceId"]) { + // Access Rights are set at workspace level + return false; + } let fullOptions = false; switch (this._resourceType) { case "study": @@ -223,7 +227,7 @@ qx.Class.define("osparc.share.Collaborators", { }, __buildLayout: function() { - if (this.__amIOwner()) { + if (this.__canIChangePermissions()) { this.__addCollaborators = this._createChildControlImpl("add-collaborator"); } this._createChildControlImpl("collaborators-list"); @@ -382,7 +386,7 @@ qx.Class.define("osparc.share.Collaborators", { ]; const accessRights = this._serializedDataCopy["accessRights"]; const collaboratorsList = []; - const showOptions = this.__amIOwner(); + const showOptions = this.__canIChangePermissions(); Object.keys(accessRights).forEach(gid => { if (Object.prototype.hasOwnProperty.call(this.__collaborators, gid)) { const collab = this.__collaborators[gid]; From 241fbda05da68f310b6b220277ebf8c365c54759 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 13:15:04 +0200 Subject: [PATCH 37/58] comment --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 1 + 1 file changed, 1 insertion(+) 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 7d83698efb1..47160d3974d 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1122,6 +1122,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { menu.addSeparator(); + // Access Rights are set at workspace level) if (writeAccess && this.getCurrentWorkspaceId() === null) { const shareButton = this._getShareMenuButton(card); if (shareButton) { From 77eb0c1954a8dc795621747fddeabc2d9d6d14bc Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 14:35:46 +0200 Subject: [PATCH 38/58] get folders --- .../class/osparc/dashboard/StudyBrowser.js | 19 ++++++++++--------- .../source/class/osparc/store/Folders.js | 5 +++-- 2 files changed, 13 insertions(+), 11 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 47160d3974d..b1290a44595 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -109,13 +109,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // overridden initResources: function() { this._resourcesList = []; - const promises = [ - this.__getActiveStudy() - ]; - if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { - promises.push(osparc.store.Folders.getInstance().fetchFolders()); - } - Promise.all(promises) + this.__getActiveStudy() .then(() => { this.getChildControl("resources-layout"); this.__attachEventHandlers(); @@ -176,8 +170,15 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __reloadFolders: function() { - const folders = osparc.store.Folders.getInstance().getFolders(this.getCurrentFolderId()) - this.__setFoldersToList(folders); + if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { + const folderId = this.getCurrentFolderId(); + const workspaceId = this.getCurrentWorkspaceId(); + osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId) + .then(folders => { + this.__setFoldersToList(folders); + }) + .catch(console.error); + } }, __reloadStudies: function() { diff --git a/services/static-webserver/client/source/class/osparc/store/Folders.js b/services/static-webserver/client/source/class/osparc/store/Folders.js index cb4f243e2c1..351e090f6f2 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -28,7 +28,7 @@ qx.Class.define("osparc.store.Folders", { members: { foldersCached: null, - fetchFolders: function(folderId = null) { + fetchFolders: function(folderId = null, workspaceId = null) { if (osparc.auth.Data.getInstance().isGuest()) { return new Promise(resolve => { resolve([]); @@ -37,7 +37,8 @@ qx.Class.define("osparc.store.Folders", { const params = { "url": { - folderId + workspaceId, + folderId, } }; return osparc.data.Resources.getInstance().getAllPages("folders", params) From 19678f921e38b0772a845b817cc26afa887fe5ce Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:06:00 +0200 Subject: [PATCH 39/58] Shares studies --- .../source/class/osparc/dashboard/ResourceFilter.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js index 5e3023b13f0..97bf486c2ce 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js @@ -92,7 +92,15 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { button.set({ label: this.tr("My Workspace") }); - } else { + } else if (option.id === "shared-with-me") { + button.set({ + label: this.tr("Shared") + " " + osparc.product.Utils.getStudyAlias({ + firstUpperCase: true, + plural: true, + }) + }); + } + if (option.id !== "show-all") { button.set({ marginLeft: 15 }); From f83afca2d16d9df9f8f36f47544bb6bfd1fec87e Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:27:54 +0200 Subject: [PATCH 40/58] connect access rights --- .../class/osparc/dashboard/StudyBrowser.js | 5 +- .../source/class/osparc/store/Workspaces.js | 101 ++++++++++-------- 2 files changed, 62 insertions(+), 44 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 b1290a44595..f4278744198 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -436,7 +436,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { _deleteWorkspaceRequested: function(workspaceId) { osparc.store.Workspaces.deleteWorkspace(workspaceId) - .then(() => this.__reloadWorkspaceCards()) + .then(() => { + this._resourceFilter.reloadWorkspaceButtons(); + this.__reloadWorkspaces(); + }) .catch(err => console.error(err)); }, // /WORKSPACES diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 794a5d0f456..5a0a43239c4 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -111,56 +111,71 @@ qx.Class.define("osparc.store.Workspaces", { }, addCollaborators: function(workspaceId, newCollaborators) { - return new Promise((resolve, reject) => { - const workspace = this.getWorkspace(workspaceId); - if (workspace) { - const accessRights = workspace.getAccessRights(); - const newAccessRights = Object.assign(accessRights, newCollaborators); + const promises = []; + Object.keys(newCollaborators).forEach(groupId => { + const params = { + url: { + workspaceId, + groupId, + }, + data: newCollaborators[groupId] + }; + promises.push(osparc.data.Resources.fetch("studies", "postAccessRights", params)); + }); + return Promise.all(promises) + .then(() => { + const workspace = this.getWorkspace(workspaceId); + const newAccessRights = workspace.getAccessRights(); + Object.keys(newCollaborators).forEach(gid => { + newAccessRights[gid] = newCollaborators[gid]; + }); workspace.set({ accessRights: newAccessRights, - lastModified: new Date() - }) - resolve(); - } else { - reject(); - } - }); + modifiedAt: new Date() + }); + }) + .catch(console.error); }, - removeCollaborator: function(workspaceId, gid) { - return new Promise((resolve, reject) => { - const workspace = this.getWorkspace(workspaceId); - if (workspace) { - const accessRights = workspace.getAccessRights(); - delete accessRights[gid]; - workspace.set({ - accessRights: accessRights, - lastModified: new Date() - }) - resolve(); - } else { - reject(); + removeCollaborator: function(workspaceId, groupId) { + const params = { + url: { + workspaceId, + groupId, } - }); + }; + return osparc.data.Resources.fetch("studies", "deleteAccessRights", params) + .then(() => { + const workspace = this.getWorkspace(workspaceId); + const newAccessRights = workspace.getAccessRights(); + delete newAccessRights[groupId]; + workspace.set({ + accessRights: newAccessRights, + modifiedAt: new Date() + }); + }) + .catch(console.error); }, - updateCollaborator: function(workspaceId, gid, newPermissions) { - return new Promise((resolve, reject) => { - const workspace = this.getWorkspace(workspaceId); - if (workspace) { - const accessRights = workspace.getAccessRights(); - if (gid in accessRights) { - accessRights[gid] = newPermissions; - workspace.set({ - accessRights: accessRights, - lastModified: new Date() - }) - resolve(); - return; - } - } - reject(); - }); + updateCollaborator: function(workspaceId, groupId, newPermissions) { + const params = { + url: { + workspaceId, + groupId, + }, + data: newPermissions + }; + return osparc.data.Resources.fetch("studies", "putAccessRights", params) + .then(() => { + const workspace = this.getWorkspace(workspaceId); + const newAccessRights = workspace.getAccessRights(); + newAccessRights[groupId] = newPermissions; + workspace.set({ + accessRights: workspace.newAccessRights, + modifiedAt: new Date() + }); + }) + .catch(console.error); }, getWorkspaces: function(parentId = null) { From 25974f6f20371e84d2f4a46f431cd9bf0ef39d3c Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:36:04 +0200 Subject: [PATCH 41/58] minor --- .../client/source/class/osparc/store/Workspaces.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 5a0a43239c4..39042c7743c 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -120,7 +120,7 @@ qx.Class.define("osparc.store.Workspaces", { }, data: newCollaborators[groupId] }; - promises.push(osparc.data.Resources.fetch("studies", "postAccessRights", params)); + promises.push(osparc.data.Resources.fetch("workspaces", "postAccessRights", params)); }); return Promise.all(promises) .then(() => { @@ -144,7 +144,7 @@ qx.Class.define("osparc.store.Workspaces", { groupId, } }; - return osparc.data.Resources.fetch("studies", "deleteAccessRights", params) + return osparc.data.Resources.fetch("workspaces", "deleteAccessRights", params) .then(() => { const workspace = this.getWorkspace(workspaceId); const newAccessRights = workspace.getAccessRights(); @@ -165,7 +165,7 @@ qx.Class.define("osparc.store.Workspaces", { }, data: newPermissions }; - return osparc.data.Resources.fetch("studies", "putAccessRights", params) + return osparc.data.Resources.fetch("workspaces", "putAccessRights", params) .then(() => { const workspace = this.getWorkspace(workspaceId); const newAccessRights = workspace.getAccessRights(); From 66d96d8644b4191ee15d7ae86f82fa06ec5f256e Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:38:37 +0200 Subject: [PATCH 42/58] minor --- .../client/source/class/osparc/dashboard/ResourceFilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js index 97bf486c2ce..4a93a2b0d00 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js @@ -152,8 +152,8 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { const workspaceButton = this.__workspaceButtons[i]; this.__contextLayout.remove(workspaceButton); this.__contextRadioGroup.remove(workspaceButton); - this.__workspaceButtons = []; } + this.__workspaceButtons = []; osparc.store.Workspaces.fetchWorkspaces() .then(workspaces => { workspaces.forEach(workspace => { From e5736e9cac1f26f428ab84a84e43f18f5062123e Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:42:33 +0200 Subject: [PATCH 43/58] update thumbnail --- .../source/class/osparc/dashboard/WorkspaceButtonItem.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index cd5fa3be2a6..84e3f932ca6 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -186,16 +186,19 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { const newWorkspace = false; const workspaceEditor = new osparc.editor.WorkspaceEditor(newWorkspace).set({ label: workspace.getName(), - description: workspace.getDescription() + description: workspace.getDescription(), + thumbnail: workspace.getThumbnail(), }); const title = this.tr("Edit Workspace"); const win = osparc.ui.window.Window.popUpInWindow(workspaceEditor, title, 300, 200); workspaceEditor.addListener("updateWorkspace", () => { const newName = workspaceEditor.getLabel(); const newDescription = workspaceEditor.getDescription(); + const newThumbnail = workspaceEditor.getThumbnail(); const updateData = { "name": newName, - "description": newDescription + "description": newDescription, + "thumbnail": newThumbnail, }; osparc.store.Workspaces.putWorkspace(this.getWorkspaceId(), updateData) .then(() => { From e415b4d4045023564f1ee63f4e6fea9ce4cdbd0e Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 15:57:27 +0200 Subject: [PATCH 44/58] last --- .../class/osparc/dashboard/WorkspaceButtonBase.js | 11 +++++++++-- .../client/source/class/osparc/store/Workspaces.js | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonBase.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonBase.js index fbcf92b3bf7..844004de698 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonBase.js @@ -192,14 +192,18 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonBase", { // overridden _applyIcon: function(value) { + const image = this.getChildControl("icon").getChildControl("image"); if ( value.includes("@FontAwesome5Solid/") || value.includes("@MaterialIcons/") ) { + this.getContentElement().setStyles({ + "background-image": "none", + }); value += this.self().ICON_SIZE; - const image = this.getChildControl("icon").getChildControl("image"); image.set({ - source: value + source: value, + visibility: "visible", }); } else { this.getContentElement().setStyles({ @@ -209,6 +213,9 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonBase", { "background-position": "center center", "background-origin": "border-box" }); + image.set({ + visibility: "excluded", + }); } }, diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 39042c7743c..839670ffb24 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -103,7 +103,9 @@ qx.Class.define("osparc.store.Workspaces", { workspace[setter](updateData[propKey]); } }); - workspace.setLastModified(new Date()); + workspace.set({ + modifiedAt: new Date() + }); resolve(workspace); }) .catch(err => reject(err)); From e80982e00d8a004778464e7d37a0b58a9f86c8b5 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:00:24 +0200 Subject: [PATCH 45/58] Rename -> Edit --- .../client/source/class/osparc/dashboard/WorkspaceButtonItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index 84e3f932ca6..2b49a333b8a 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -180,7 +180,7 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { position: "bottom-right" }); - const editButton = new qx.ui.menu.Button(this.tr("Rename..."), "@FontAwesome5Solid/pencil-alt/12"); + const editButton = new qx.ui.menu.Button(this.tr("Edit..."), "@FontAwesome5Solid/pencil-alt/12"); editButton.addListener("execute", () => { const workspace = this.getWorkspace(); const newWorkspace = false; From 2dee0c8224ec2373dc75ed9794226231fc58ff2b Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:07:37 +0200 Subject: [PATCH 46/58] minors --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 6 ++++-- .../client/source/class/osparc/data/model/Folder.js | 8 -------- 2 files changed, 4 insertions(+), 10 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 f4278744198..872ce747e5f 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -445,9 +445,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // /WORKSPACES // FOLDERS - __applyCurrentFolderId: function(currentFolderId) { + __applyCurrentFolderId: function() { if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { - osparc.store.Folders.getInstance().fetchFolders(currentFolderId) + const folderId = this.getCurrentFolderId(); + const workspaceId = this.getCurrentWorkspaceId(); + osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId) .then(() => { this._resourcesContainer.setResourcesToList([]); this._resourcesList = []; diff --git a/services/static-webserver/client/source/class/osparc/data/model/Folder.js b/services/static-webserver/client/source/class/osparc/data/model/Folder.js index 687b3d8edc7..d9d2b517212 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Folder.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Folder.js @@ -33,7 +33,6 @@ qx.Class.define("osparc.data.model.Folder", { parentId: folderData.parentFolderId, name: folderData.name, myAccessRights: folderData.myAccessRights, - accessRights: folderData.accessRights, owner: folderData.owner, createdAt: new Date(folderData.createdAt), lastModified: new Date(folderData.modifiedAt), @@ -69,13 +68,6 @@ qx.Class.define("osparc.data.model.Folder", { event: "changeMyAccessRights" }, - accessRights: { - check: "Object", - nullable: false, - init: null, - event: "changeAccessRights" - }, - owner: { check: "Number", nullable: true, From 4fc74d7cd5293b86fa7f349ce24feac29692b692 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:10:33 +0200 Subject: [PATCH 47/58] createMinStudyObject --- .../source/class/osparc/data/model/Study.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index cada618f83a..0df98435304 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -215,11 +215,19 @@ qx.Class.define("osparc.data.model.Study", { ], createMinStudyObject: function() { - const newStudyObject = { - "name": "", - "workbench": {}, - }; - return newStudyObject; + let myNewStudyObject = {}; + const props = qx.util.PropertyUtil.getProperties(osparc.data.model.Study); + for (let key in props) { + const prop = props[key]; + if (!prop.nullable) { + if (typeof prop.init === "object") { + myNewStudyObject[key] = osparc.utils.Utils.deepCloneObject(prop.init); + } else { + myNewStudyObject[key] = prop.init; + } + } + } + return myNewStudyObject; }, getProperties: function() { From 1000ad2bab39ab75bb24c9c9f08184562d697307 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:29:24 +0200 Subject: [PATCH 48/58] moveToWorkspace --- .../osparc/dashboard/MoveStudyToWorkspace.js | 92 +++++++++++++++++++ .../class/osparc/dashboard/StudyBrowser.js | 78 ++++++++++++---- .../source/class/osparc/data/Resources.js | 6 +- 3 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js new file mode 100644 index 00000000000..528c7dcb6dc --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js @@ -0,0 +1,92 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.MoveStudyToWorkspace", { + extend: qx.ui.core.Widget, + + construct: function(studyData, currentWorkspaceId) { + this.base(arguments); + + this.__studyData = studyData; + this.__currentWorkspaceId = currentWorkspaceId; + + this._setLayout(new qx.ui.layout.VBox(10)); + + this.getChildControl("current-workspace"); + const workspacesTree = this.getChildControl("workspaces-tree"); + this.getChildControl("cancel-btn"); + const moveButton = this.getChildControl("move-btn"); + + moveButton.setEnabled(false) + workspacesTree.addListener("selectionChanged", e => { + const workspaceId = e.getData(); + moveButton.setEnabled(this.__currentWorkspaceId !== workspaceId); + moveButton.addListenerOnce("execute", () => this.fireDataEvent("moveToWorkspace", workspaceId)); + }); + }, + + events: { + "cancel": "qx.event.type.Event", + "moveToWorkspace": "qx.event.type.Data" + }, + + members: { + __studyData: null, + __currentWorkspaceId: null, + + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "current-workspace": { + const workspace = osparc.store.Workspaces.getInstance().getWorkspace(this.__currentWorkspaceId); + const currentWorkspaceName = workspace ? workspace["name"] : "Home"; + control = new qx.ui.basic.Label(this.tr("Current location: ") + currentWorkspaceName); + this._add(control); + break; + } + case "workspaces-tree": + control = new osparc.dashboard.WorkspacesTree(); + this._add(control); + break; + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ + alignX: "right" + })); + this._add(control); + break; + case "cancel-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Cancel")).set({ + appearance: "form-button-text" + }); + control.addListener("execute", () => this.fireEvent("cancel"), this); + buttons.add(control); + break; + } + case "move-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Move")).set({ + appearance: "form-button" + }); + buttons.add(control); + break; + } + } + return control || this.base(arguments, id); + } + } +}); 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 872ce747e5f..501a63e037e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1075,6 +1075,16 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return osparc.data.Resources.fetch("studies", "moveToFolder", params); }, + __moveStudyToWorkspace: function(studyId, workspaceId) { + const params = { + url: { + studyId, + folderId: workspaceId + } + }; + return osparc.data.Resources.fetch("studies", "moveToWorkspace", params); + }, + _updateStudyData: function(studyData) { studyData["resourceType"] = "study"; const studies = this._resourcesList; @@ -1152,10 +1162,17 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } if (writeAccess && osparc.utils.DisabledPlugins.isFoldersEnabled()) { + menu.addSeparator(); + const moveToFolderButton = this.__getMoveToFolderMenuButton(studyData); if (moveToFolderButton) { menu.add(moveToFolderButton); } + + const moveToWorkspaceButton = this.__getMoveToWorkspaceMenuButton(studyData); + if (moveToWorkspaceButton) { + menu.add(moveToWorkspaceButton); + } } if (deleteAccess) { @@ -1245,30 +1262,51 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const moveToFolderButton = new qx.ui.menu.Button(text, "@FontAwesome5Solid/folder/12"); moveToFolderButton["moveToFolderButton"] = true; moveToFolderButton.addListener("tap", () => { - if (Object.keys(studyData["accessRights"]).length > 1) { - osparc.FlashMessenger.getInstance().logAs(this.tr("Shared projects can't be moved yet"), "WARNING"); - } else { - const title = this.tr("Move") + " " + studyData["name"]; - const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId()); - const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); - moveStudyToFolder.addListener("moveToFolder", e => { - win.close(); - const folderId = e.getData(); - this.__moveStudyToFolder(studyData["uuid"], folderId) - .then(() => { - this.__removeFromStudyList(studyData["uuid"]); - }) - .catch(err => { - console.error(err); - osparc.FlashMessenger.logAs(err.message, "ERROR"); - }); - }, this); - moveStudyToFolder.addListener("cancel", () => win.close()); - } + const title = this.tr("Move") + " " + studyData["name"]; + const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId()); + const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); + moveStudyToFolder.addListener("moveToFolder", e => { + win.close(); + const folderId = e.getData(); + this.__moveStudyToFolder(studyData["uuid"], folderId) + .then(() => { + this.__removeFromStudyList(studyData["uuid"]); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.logAs(err.message, "ERROR"); + }); + }, this); + moveStudyToFolder.addListener("cancel", () => win.close()); }, this); return moveToFolderButton; }, + __getMoveToWorkspaceMenuButton: function(studyData) { + const text = osparc.utils.Utils.capitalize(this.tr("Move to Workspace...")); + const moveToWorkspaceButton = new qx.ui.menu.Button(text, osparc.store.Workspaces.iconPath(14)); + moveToWorkspaceButton["moveToWorkspaceButton"] = true; + moveToWorkspaceButton.addListener("tap", () => { + const title = this.tr("Move") + " " + studyData["name"]; + const moveStudyToWorkspace = new osparc.dashboard.MoveStudyToWorkspace(studyData, this.getCurrentFolderId()); + const win = osparc.ui.window.Window.popUpInWindow(moveStudyToWorkspace, title, 350, 280); + moveStudyToWorkspace.addListener("moveToWorkspace", e => { + win.close(); + const workspaceId = e.getData(); + this.__moveStudyToWorkspace(studyData["uuid"], workspaceId) + .then(() => { + this.__removeFromStudyList(studyData["uuid"]); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.logAs(err.message, "ERROR"); + }); + }, this); + moveStudyToWorkspace.addListener("cancel", () => win.close()); + }, this); + return moveToWorkspaceButton; + }, + __getDuplicateMenuButton: function(studyData) { const duplicateButton = new qx.ui.menu.Button(this.tr("Duplicate"), "@FontAwesome5Solid/copy/12"); duplicateButton["duplicateButton"] = true; diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 8713036afb0..86d935c1731 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -276,7 +276,11 @@ qx.Class.define("osparc.data.Resources", { moveToFolder: { method: "PUT", url: statics.API + "/projects/{studyId}/folders/{folderId}" - } + }, + moveToWorkspace: { + method: "PUT", + url: statics.API + "/projects/{studyId}/workspace/{workspaceId}" + }, } }, "studyComments": { From 896fb1bc548ca62d76b192aed01aa2f3f4ada368 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:50:32 +0200 Subject: [PATCH 49/58] move to workspaces --- .../class/osparc/dashboard/FoldersTree.js | 19 ++- .../osparc/dashboard/MoveStudyToFolder.js | 5 +- .../osparc/dashboard/MoveStudyToWorkspace.js | 2 +- .../class/osparc/dashboard/StudyBrowser.js | 4 +- .../osparc/dashboard/WorkspaceTreeItem.js | 41 ++++++ .../class/osparc/dashboard/WorkspacesTree.js | 123 ++++++++++++++++++ 6 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 services/static-webserver/client/source/class/osparc/dashboard/WorkspaceTreeItem.js create mode 100644 services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js b/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js index 2a597b08b92..059a01c49f2 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js @@ -18,8 +18,11 @@ qx.Class.define("osparc.dashboard.FoldersTree", { extend: qx.ui.tree.VirtualTree, - construct: function() { - const rootFolder = this.self().createNewEntry(null); + construct: function(currentWorkspaceId) { + this.__currentWorkspaceId = currentWorkspaceId; + const workspace = osparc.store.Workspaces.getWorkspace(currentWorkspaceId); + const rootLabel = workspace ? workspace.getName() : "My Workspace"; + const rootFolder = this.self().createNewEntry(rootLabel, null); const root = qx.data.marshal.Json.createModel(rootFolder, true); this.__fetchChildren(root); @@ -41,10 +44,10 @@ qx.Class.define("osparc.dashboard.FoldersTree", { }, statics: { - createNewEntry: function(folder) { + createNewEntry: function(label, folderId) { return { - folderId: folder ? folder.getFolderId() : null, - label: folder ? folder.getName() : "Home", + label, + folderId, children: [ this.self().getLoadingData() ], @@ -77,6 +80,8 @@ qx.Class.define("osparc.dashboard.FoldersTree", { }, members: { + __currentWorkspaceId:null, + __initTree: function() { const that = this; this.setDelegate({ @@ -116,11 +121,11 @@ qx.Class.define("osparc.dashboard.FoldersTree", { parentModel.setLoaded(true); const folderId = parentModel.getFolderId ? parentModel.getFolderId() : parentModel.getModel(); - osparc.store.Folders.getInstance().fetchFolders(folderId) + osparc.store.Folders.getInstance().fetchFolders(folderId, this.__currentWorkspaceId) .then(folders => { this.self().removeLoadingChild(parentModel); folders.forEach(folder => { - const folderData = this.self().createNewEntry(folder); + const folderData = this.self().createNewEntry(folder.getName(), folder.getFolderId()); const folderModel = qx.data.marshal.Json.createModel(folderData, true); parentModel.getChildren().append(folderModel); }); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js index f01d64f42b4..4f087fb41f9 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js @@ -18,11 +18,12 @@ qx.Class.define("osparc.dashboard.MoveStudyToFolder", { extend: qx.ui.core.Widget, - construct: function(studyData, currentFolderId) { + construct: function(studyData, currentFolderId, currentWorkspaceId) { this.base(arguments); this.__studyData = studyData; this.__currentFolderId = currentFolderId; + this.__currentWorkspaceId = currentWorkspaceId; this._setLayout(new qx.ui.layout.VBox(10)); @@ -59,7 +60,7 @@ qx.Class.define("osparc.dashboard.MoveStudyToFolder", { break; } case "folders-tree": - control = new osparc.dashboard.FoldersTree(); + control = new osparc.dashboard.FoldersTree(this.__currentWorkspaceId); this._add(control); break; case "buttons-layout": diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js index 528c7dcb6dc..18915045631 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js @@ -52,7 +52,7 @@ qx.Class.define("osparc.dashboard.MoveStudyToWorkspace", { let control; switch (id) { case "current-workspace": { - const workspace = osparc.store.Workspaces.getInstance().getWorkspace(this.__currentWorkspaceId); + const workspace = osparc.store.Workspaces.getWorkspace(this.__currentWorkspaceId); const currentWorkspaceName = workspace ? workspace["name"] : "Home"; control = new qx.ui.basic.Label(this.tr("Current location: ") + currentWorkspaceName); this._add(control); 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 501a63e037e..ec1f9b943bd 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1079,7 +1079,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const params = { url: { studyId, - folderId: workspaceId + workspaceId, } }; return osparc.data.Resources.fetch("studies", "moveToWorkspace", params); @@ -1263,7 +1263,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { moveToFolderButton["moveToFolderButton"] = true; moveToFolderButton.addListener("tap", () => { const title = this.tr("Move") + " " + studyData["name"]; - const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId()); + const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId(), this.getCurrentWorkspaceId()); const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); moveStudyToFolder.addListener("moveToFolder", e => { win.close(); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceTreeItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceTreeItem.js new file mode 100644 index 00000000000..f6959bb446b --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceTreeItem.js @@ -0,0 +1,41 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.WorkspaceTreeItem", { + extend: qx.ui.tree.VirtualTreeItem, + + members: { + _addWidgets: function() { + this.addSpacer(); + // this.addOpenButton(); + const openButton = this.getChildControl("open"); + openButton.addListener("changeOpen", () => { + console.log("changeOpen", this); + }, this); + openButton.addListener("changeVisibility", e => { + // console.log("changeVisibility", this.getLabel() + e.getData(), this); + openButton.show(); + }, this); + this._add(openButton); + this.addIcon(); + const label = this.getChildControl("label"); + this._add(label, { + flex: 1 + }); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js new file mode 100644 index 00000000000..6552ef90f8b --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js @@ -0,0 +1,123 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.WorkspacesTree", { + extend: qx.ui.tree.VirtualTree, + + construct: function() { + const rootLabel = "Root"; + const rootWorkspace = this.self().createNewEntry(rootLabel, null); + const root = qx.data.marshal.Json.createModel(rootWorkspace, true); + this.__fetchChildren(root); + + this.base(arguments, root, "label", "children"); + + this.set({ + openMode: "dbltap", + decorator: "no-border", + font: "text-14", + showLeafs: true, + paddingLeft: -10, + hideRoot: true + }); + + this.__initTree(); + }, + + events: { + "selectionChanged": "qx.event.type.Event" // tap + }, + + statics: { + createNewEntry: function(label, workspaceId) { + return { + label, + workspaceId, + children: [ + this.self().getLoadingData() + ], + loaded: false, + }; + }, + + getLoadingData: function() { + return { + workspaceId: -1, + label: "Loading...", + children: [], + icon: "@FontAwesome5Solid/circle-notch/12", + loaded: false, + }; + }, + + addLoadingChild: function(parentModel) { + const loadingModel = qx.data.marshal.Json.createModel(this.self().getLoadingData(), true); + parentModel.getChildren().append(loadingModel); + }, + + removeLoadingChild: function(parent) { + for (let i = parent.getChildren().getLength() - 1; i >= 0; i--) { + if (parent.getChildren().toArray()[i].getLabel() === "Loading...") { + parent.getChildren().splice(i, 1); + } + } + } + }, + + members: { + __currentWorkspaceId:null, + + __initTree: function() { + this.setDelegate({ + createItem: () => new osparc.dashboard.WorkspaceTreeItem(), + bindItem: (c, item, id) => { + c.bindDefaultProperties(item, id); + c.bindProperty("workspaceId", "model", null, item, id); + }, + configureItem: item => { + item.addListener("tap", () => this.fireDataEvent("selectionChanged", item.getModel()), this); + }, + sorter: (a, b) => { + const aLabel = a.getLabel(); + if (aLabel === -1) { + return 1; + } + const bLabel = b.getLabel(); + if (bLabel === -1) { + return -1; + } + return aLabel - bLabel; + } + }); + }, + + __fetchChildren: function(parentModel) { + parentModel.setLoaded(true); + + osparc.store.Workspaces.fetchWorkspaces() + .then(workspaces => { + this.self().removeLoadingChild(parentModel); + workspaces.forEach(workspace => { + const workspaceData = this.self().createNewEntry(workspace.getName(), workspace.getWorkspaceId()); + const workspaceModel = qx.data.marshal.Json.createModel(workspaceData, true); + parentModel.getChildren().append(workspaceModel); + }); + }) + .catch(console.error); + } + } +}); From b9cbe031708dc44d088c2322f4d5001363233195 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 16:58:43 +0200 Subject: [PATCH 50/58] minor --- .../source/class/osparc/dashboard/MoveStudyToWorkspace.js | 2 +- .../client/source/class/osparc/dashboard/WorkspacesTree.js | 4 ++++ .../client/source/class/osparc/data/Resources.js | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js index 18915045631..27148d80f5d 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js @@ -53,7 +53,7 @@ qx.Class.define("osparc.dashboard.MoveStudyToWorkspace", { switch (id) { case "current-workspace": { const workspace = osparc.store.Workspaces.getWorkspace(this.__currentWorkspaceId); - const currentWorkspaceName = workspace ? workspace["name"] : "Home"; + const currentWorkspaceName = workspace ? workspace.getName() : "My Workspace"; control = new qx.ui.basic.Label(this.tr("Current location: ") + currentWorkspaceName); this._add(control); break; diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js index 6552ef90f8b..0c0c2ce078e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesTree.js @@ -108,6 +108,10 @@ qx.Class.define("osparc.dashboard.WorkspacesTree", { __fetchChildren: function(parentModel) { parentModel.setLoaded(true); + const myWorkspaceData = this.self().createNewEntry("My Workspace", null); + const myWorkspaceModel = qx.data.marshal.Json.createModel(myWorkspaceData, true); + parentModel.getChildren().append(myWorkspaceModel); + osparc.store.Workspaces.fetchWorkspaces() .then(workspaces => { this.self().removeLoadingChild(parentModel); diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 86d935c1731..6a2edb1f216 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -279,7 +279,7 @@ qx.Class.define("osparc.data.Resources", { }, moveToWorkspace: { method: "PUT", - url: statics.API + "/projects/{studyId}/workspace/{workspaceId}" + url: statics.API + "/projects/{studyId}/workspaces/{workspaceId}" }, } }, From 15b84bc98bc29cc024d3fd0e0fb503de97711291 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Thu, 5 Sep 2024 17:04:23 +0200 Subject: [PATCH 51/58] Move to workspace --- .../source/class/osparc/dashboard/MoveStudyToFolder.js | 5 ++++- .../source/class/osparc/dashboard/MoveStudyToWorkspace.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js index 4f087fb41f9..6fe27281aac 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js @@ -36,7 +36,10 @@ qx.Class.define("osparc.dashboard.MoveStudyToFolder", { foldersTree.addListener("selectionChanged", e => { const folderId = e.getData(); moveButton.setEnabled(this.__currentFolderId !== folderId); - moveButton.addListenerOnce("execute", () => this.fireDataEvent("moveToFolder", folderId)); + this.__selectedFolderId = folderId; + }); + moveButton.addListener("execute", () => { + this.fireDataEvent("moveToFolder", this.__selectedFolderId); }); }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js index 27148d80f5d..b7cc08d8290 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js @@ -35,8 +35,11 @@ qx.Class.define("osparc.dashboard.MoveStudyToWorkspace", { workspacesTree.addListener("selectionChanged", e => { const workspaceId = e.getData(); moveButton.setEnabled(this.__currentWorkspaceId !== workspaceId); - moveButton.addListenerOnce("execute", () => this.fireDataEvent("moveToWorkspace", workspaceId)); + this.__selectedWorkspaceId = workspaceId; }); + moveButton.addListener("execute", () => { + this.fireDataEvent("moveToWorkspace", this.__selectedWorkspaceId); + }, this); }, events: { From 2d5ce7da6c0955fa035e991132289b28e26625e8 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:13:49 +0200 Subject: [PATCH 52/58] Move Folder to Folder --- .../osparc/dashboard/FolderButtonItem.js | 70 ++++++++++++------- ...udyToFolder.js => MoveResourceToFolder.js} | 6 +- ...orkspace.js => MoveResourceToWorkspace.js} | 2 +- .../class/osparc/dashboard/StudyBrowser.js | 8 +-- 4 files changed, 52 insertions(+), 34 deletions(-) rename services/static-webserver/client/source/class/osparc/dashboard/{MoveStudyToFolder.js => MoveResourceToFolder.js} (93%) rename services/static-webserver/client/source/class/osparc/dashboard/{MoveStudyToWorkspace.js => MoveResourceToWorkspace.js} (97%) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js index 18b997e3fb3..92752d3b2f8 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -163,33 +163,15 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { }); const editButton = new qx.ui.menu.Button(this.tr("Rename..."), "@FontAwesome5Solid/pencil-alt/12"); - editButton.addListener("execute", () => { - const folder = this.getFolder(); - const newFolder = false; - const folderEditor = new osparc.editor.FolderEditor(newFolder).set({ - label: folder.getName(), - }); - const title = this.tr("Edit Folder"); - const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 150); - folderEditor.addListener("updateFolder", () => { - const newName = folderEditor.getLabel(); - const updateData = { - "name": newName, - }; - osparc.data.model.Folder.putFolder(this.getFolderId(), updateData) - .then(() => { - folder.set({ - name: newName, - }); - this.fireDataEvent("folderUpdated", folder.getFolderId()); - }) - .catch(err => console.error(err)); - win.close(); - }); - folderEditor.addListener("cancel", () => win.close()); - }); + editButton.addListener("execute", () => this.__editFolder(), this); menu.add(editButton); + const moveToFolderButton = new qx.ui.menu.Button(this.tr("Move to Folder..."), "@FontAwesome5Solid/folder/12"); + moveToFolderButton.addListener("execute", () => this.__moveToFolder(), this); + menu.add(moveToFolderButton); + + menu.addSeparator(); + const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); deleteButton.addListener("execute", () => this.__deleteStudyRequested(), this); menu.add(deleteButton); @@ -204,6 +186,44 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { this.setValue(false); }, + __editFolder: function() { + const folder = this.getFolder(); + const newFolder = false; + const folderEditor = new osparc.editor.FolderEditor(newFolder).set({ + label: folder.getName(), + }); + const title = this.tr("Edit Folder"); + const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 150); + folderEditor.addListener("updateFolder", () => { + const newName = folderEditor.getLabel(); + const updateData = { + "name": newName, + }; + osparc.data.model.Folder.putFolder(this.getFolderId(), updateData) + .then(() => { + folder.set({ + name: newName, + }); + this.fireDataEvent("folderUpdated", folder.getFolderId()); + }) + .catch(err => console.error(err)); + win.close(); + }); + folderEditor.addListener("cancel", () => win.close()); + }, + + __moveToFolder: function() { + const folder = this.getFolder(); + const title = this.tr("Move") + " " + folder.getName(); + const moveStudyToFolder = new osparc.dashboard.MoveResourceToFolder(this.getCurrentFolderId(), this.getCurrentWorkspaceId()); + const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); + moveStudyToFolder.addListener("moveToFolder", e => { + win.close(); + const folderId = e.getData(); + console.log(folderId); + }, this); + }, + __deleteStudyRequested: function() { const msg = this.tr("Are you sure you want to delete") + " " + this.getTitle() + "?"; const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToFolder.js similarity index 93% rename from services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js rename to services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToFolder.js index 6fe27281aac..e01bc834776 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToFolder.js @@ -15,13 +15,12 @@ ************************************************************************ */ -qx.Class.define("osparc.dashboard.MoveStudyToFolder", { +qx.Class.define("osparc.dashboard.MoveResourceToFolder", { extend: qx.ui.core.Widget, - construct: function(studyData, currentFolderId, currentWorkspaceId) { + construct: function(currentFolderId, currentWorkspaceId) { this.base(arguments); - this.__studyData = studyData; this.__currentFolderId = currentFolderId; this.__currentWorkspaceId = currentWorkspaceId; @@ -49,7 +48,6 @@ qx.Class.define("osparc.dashboard.MoveStudyToFolder", { }, members: { - __studyData: null, __currentFolderId: null, _createChildControlImpl: function(id) { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js similarity index 97% rename from services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js rename to services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js index b7cc08d8290..9c46b545cef 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js @@ -15,7 +15,7 @@ ************************************************************************ */ -qx.Class.define("osparc.dashboard.MoveStudyToWorkspace", { +qx.Class.define("osparc.dashboard.MoveResourceToWorkspace", { extend: qx.ui.core.Widget, construct: function(studyData, currentWorkspaceId) { 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 ec1f9b943bd..b9deea1e1cc 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1258,12 +1258,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __getMoveToFolderMenuButton: function(studyData) { - const text = osparc.utils.Utils.capitalize(this.tr("Move to Folder...")); + const text = this.tr("Move to Folder..."); const moveToFolderButton = new qx.ui.menu.Button(text, "@FontAwesome5Solid/folder/12"); moveToFolderButton["moveToFolderButton"] = true; moveToFolderButton.addListener("tap", () => { const title = this.tr("Move") + " " + studyData["name"]; - const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId(), this.getCurrentWorkspaceId()); + const moveStudyToFolder = new osparc.dashboard.MoveResourceToFolder(this.getCurrentFolderId(), this.getCurrentWorkspaceId()); const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); moveStudyToFolder.addListener("moveToFolder", e => { win.close(); @@ -1283,12 +1283,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __getMoveToWorkspaceMenuButton: function(studyData) { - const text = osparc.utils.Utils.capitalize(this.tr("Move to Workspace...")); + const text = this.tr("Move to Workspace..."); const moveToWorkspaceButton = new qx.ui.menu.Button(text, osparc.store.Workspaces.iconPath(14)); moveToWorkspaceButton["moveToWorkspaceButton"] = true; moveToWorkspaceButton.addListener("tap", () => { const title = this.tr("Move") + " " + studyData["name"]; - const moveStudyToWorkspace = new osparc.dashboard.MoveStudyToWorkspace(studyData, this.getCurrentFolderId()); + const moveStudyToWorkspace = new osparc.dashboard.MoveResourceToWorkspace(studyData, this.getCurrentFolderId()); const win = osparc.ui.window.Window.popUpInWindow(moveStudyToWorkspace, title, 350, 280); moveStudyToWorkspace.addListener("moveToWorkspace", e => { win.close(); From 28c939a9ee3318591cac2f0d441f10c8b694c571 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:20:18 +0200 Subject: [PATCH 53/58] minor fix --- .../client/source/class/osparc/auth/ui/LoginView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js b/services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js index 1c18a4a3aed..1cd8440fc45 100644 --- a/services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js +++ b/services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js @@ -99,7 +99,7 @@ qx.Class.define("osparc.auth.ui.LoginView", { this.fireEvent("toRegister"); } else if (createAccountAction === "REQUEST_ACCOUNT_FORM") { this.fireEvent("toRequestAccount"); - } else if (createAccountAction === "REQUEST_ACCOUNT_FORM") { + } else if (createAccountAction === "REQUEST_ACCOUNT_INSTRUCTIONS") { osparc.store.Support.openInvitationRequiredDialog(); } createAccountBtn.setEnabled(true); From c47c10a7dc8c05b1330bb0c14e9dfcee105231b0 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:24:31 +0200 Subject: [PATCH 54/58] minor --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 b9deea1e1cc..c5b29ba64dd 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -1164,12 +1164,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (writeAccess && osparc.utils.DisabledPlugins.isFoldersEnabled()) { menu.addSeparator(); - const moveToFolderButton = this.__getMoveToFolderMenuButton(studyData); + const moveToFolderButton = this.__getMoveStudyToFolderMenuButton(studyData); if (moveToFolderButton) { menu.add(moveToFolderButton); } - const moveToWorkspaceButton = this.__getMoveToWorkspaceMenuButton(studyData); + const moveToWorkspaceButton = this.__getMoveStudyToWorkspaceMenuButton(studyData); if (moveToWorkspaceButton) { menu.add(moveToWorkspaceButton); } @@ -1257,7 +1257,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return studyBillingSettingsButton; }, - __getMoveToFolderMenuButton: function(studyData) { + __getMoveStudyToFolderMenuButton: function(studyData) { const text = this.tr("Move to Folder..."); const moveToFolderButton = new qx.ui.menu.Button(text, "@FontAwesome5Solid/folder/12"); moveToFolderButton["moveToFolderButton"] = true; @@ -1282,7 +1282,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return moveToFolderButton; }, - __getMoveToWorkspaceMenuButton: function(studyData) { + __getMoveStudyToWorkspaceMenuButton: function(studyData) { const text = this.tr("Move to Workspace..."); const moveToWorkspaceButton = new qx.ui.menu.Button(text, osparc.store.Workspaces.iconPath(14)); moveToWorkspaceButton["moveToWorkspaceButton"] = true; From 6f1c89e66f1aa9ad051c991f81bab442b260e7d9 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:24:48 +0200 Subject: [PATCH 55/58] added two more endpoints --- .../client/source/class/osparc/data/Resources.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 6a2edb1f216..4516598e2dd 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -320,7 +320,15 @@ qx.Class.define("osparc.data.Resources", { delete: { method: "DELETE", url: statics.API + "/folders/{folderId}" - } + }, + moveToFolder: { + method: "PUT", + url: statics.API + "/folders/{folderId}/folders/{destFolderId}" + }, + moveToWorkspace: { + method: "PUT", + url: statics.API + "/folders/{folderId}/folders/{workspaceId}" + }, } }, "workspaces": { From d957fd6ebf0588c4e1bd7beb805cab6702a3a530 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:28:04 +0200 Subject: [PATCH 56/58] minor --- .../client/source/class/osparc/dashboard/StudyBrowser.js | 6 +++--- 1 file changed, 3 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 c5b29ba64dd..8ad34f0fdcb 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -366,7 +366,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._resourcesContainer.setResourcesToList(this._resourcesList); const cards = this._resourcesContainer.reloadCards("studies"); - this.__configureCards(cards); + this.__configureStudyCards(cards); this.__addNewStudyButtons(); @@ -384,7 +384,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { _reloadNewCards: function() { this._resourcesContainer.setResourcesToList(this._resourcesList); const cards = this._resourcesContainer.reloadNewCards(); - this.__configureCards(cards); + this.__configureStudyCards(cards); osparc.filter.UIFilterController.dispatch("searchBarFilter"); }, @@ -497,7 +497,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, // /FOLDERS - __configureCards: function(cards) { + __configureStudyCards: function(cards) { cards.forEach(card => { card.setMultiSelectionMode(this.getMultiSelection()); card.addListener("tap", e => { From 71a6d279fe83709f5c65244e85427f539bc11885 Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 09:56:04 +0200 Subject: [PATCH 57/58] move to workspace --- .../osparc/dashboard/FolderButtonItem.js | 20 ++--- .../dashboard/MoveResourceToWorkspace.js | 4 +- .../osparc/dashboard/ResourceBrowserBase.js | 10 +++ .../dashboard/ResourceContainerManager.js | 4 + .../class/osparc/dashboard/StudyBrowser.js | 84 +++++++++++++------ 5 files changed, 81 insertions(+), 41 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js index 92752d3b2f8..9db4130e02b 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -45,6 +45,8 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { events: { "folderSelected": "qx.event.type.Data", "folderUpdated": "qx.event.type.Data", + "moveFolderToFolderRequested": "qx.event.type.Data", + "moveFolderToWorkspaceRequested": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data" }, @@ -167,9 +169,13 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { menu.add(editButton); const moveToFolderButton = new qx.ui.menu.Button(this.tr("Move to Folder..."), "@FontAwesome5Solid/folder/12"); - moveToFolderButton.addListener("execute", () => this.__moveToFolder(), this); + moveToFolderButton.addListener("execute", () => this.fireDataEvent("moveFolderToFolderRequested", this.getFolderId()), this); menu.add(moveToFolderButton); + const moveToWorkspaceButton = new qx.ui.menu.Button(this.tr("Move to Workspace..."), ""); + moveToWorkspaceButton.addListener("execute", () => this.fireDataEvent("moveFolderToWorkspaceRequested", this.getFolderId()), this); + menu.add(moveToWorkspaceButton); + menu.addSeparator(); const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); @@ -212,18 +218,6 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { folderEditor.addListener("cancel", () => win.close()); }, - __moveToFolder: function() { - const folder = this.getFolder(); - const title = this.tr("Move") + " " + folder.getName(); - const moveStudyToFolder = new osparc.dashboard.MoveResourceToFolder(this.getCurrentFolderId(), this.getCurrentWorkspaceId()); - const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); - moveStudyToFolder.addListener("moveToFolder", e => { - win.close(); - const folderId = e.getData(); - console.log(folderId); - }, this); - }, - __deleteStudyRequested: function() { const msg = this.tr("Are you sure you want to delete") + " " + this.getTitle() + "?"; const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js index 9c46b545cef..56e9d5a674f 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveResourceToWorkspace.js @@ -18,10 +18,9 @@ qx.Class.define("osparc.dashboard.MoveResourceToWorkspace", { extend: qx.ui.core.Widget, - construct: function(studyData, currentWorkspaceId) { + construct: function(currentWorkspaceId) { this.base(arguments); - this.__studyData = studyData; this.__currentWorkspaceId = currentWorkspaceId; this._setLayout(new qx.ui.layout.VBox(10)); @@ -48,7 +47,6 @@ qx.Class.define("osparc.dashboard.MoveResourceToWorkspace", { }, members: { - __studyData: null, __currentWorkspaceId: null, _createChildControlImpl: function(id) { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js index e36c379ceb7..59bde762e2e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -253,6 +253,8 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("emptyStudyClicked", e => this._deleteResourceRequested(e.getData())); resourcesContainer.addListener("folderSelected", e => this._folderSelected(e.getData())); resourcesContainer.addListener("folderUpdated", e => this._folderUpdated(e.getData())); + resourcesContainer.addListener("moveFolderToFolderRequested", e => this._moveFolderToFolderRequested(e.getData())); + resourcesContainer.addListener("moveFolderToWorkspaceRequested", e => this._moveFolderToWorkspaceRequested(e.getData())); resourcesContainer.addListener("deleteFolderRequested", e => this._deleteFolderRequested(e.getData())); resourcesContainer.addListener("workspaceSelected", e => { const workspaceId = e.getData(); @@ -474,6 +476,14 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { throw new Error("Abstract method called!"); }, + _moveFolderToFolderRequested: function(folderId) { + throw new Error("Abstract method called!"); + }, + + _moveFolderToWorkspaceRequested: function(folderId) { + throw new Error("Abstract method called!"); + }, + _deleteFolderRequested: function(folderId) { throw new Error("Abstract method called!"); }, 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 84b2e680f5f..4f7363299fe 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -86,6 +86,8 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "changeVisibility": "qx.event.type.Data", "folderSelected": "qx.event.type.Data", "folderUpdated": "qx.event.type.Data", + "moveFolderToFolderRequested": "qx.event.type.Data", + "moveFolderToWorkspaceRequested": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data", "workspaceSelected": "qx.event.type.Data", "workspaceUpdated": "qx.event.type.Data", @@ -384,6 +386,8 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { [ "folderSelected", "folderUpdated", + "moveFolderToFolderRequested", + "moveFolderToWorkspaceRequested", "deleteFolderRequested", ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); return card; 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 8ad34f0fdcb..12406da4328 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -490,6 +490,48 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.__reloadFolders(); }, + _moveFolderToFolderRequested: function(folderId) { + const moveFolderToFolder = new osparc.dashboard.MoveResourceToFolder(this.getCurrentFolderId(), this.getCurrentWorkspaceId()); + const title = "Move to Folder"; + const win = osparc.ui.window.Window.popUpInWindow(moveFolderToFolder, title, 350, 280); + moveFolderToFolder.addListener("moveToFolder", e => { + win.close(); + const destFolderId = e.getData(); + const params = { + url: { + folderId, + destFolderId, + } + }; + osparc.data.Resources.fetch("folders", "moveToFolder", params) + .then(() => { + this.__reloadFolders() + }) + .catch(err => console.error(err)); + }); + }, + + _moveFolderToWorkspaceRequested: function(folderId) { + const moveFolderToWorkspace = new osparc.dashboard.MoveResourceToWorkspace(this.getCurrentWorkspaceId()); + const title = "Move to Workspace"; + const win = osparc.ui.window.Window.popUpInWindow(moveFolderToWorkspace, title, 350, 280); + moveFolderToWorkspace.addListener("moveToWorkspace", e => { + win.close(); + const destWorkspaceId = e.getData(); + const params = { + url: { + folderId, + workspaceId: destWorkspaceId, + } + }; + osparc.data.Resources.fetch("folders", "moveToWorkspace", params) + .then(() => { + this.__reloadFolders() + }) + .catch(err => console.error(err)); + }); + }, + _deleteFolderRequested: function(folderId) { osparc.store.Folders.getInstance().deleteFolder(folderId) .then(() => this.__reloadFolders()) @@ -1065,26 +1107,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._startStudyById(studyId, openCB, cancelCB, isStudyCreation); }, - __moveStudyToFolder: function(studyId, folderId) { - const params = { - url: { - studyId, - folderId - } - }; - return osparc.data.Resources.fetch("studies", "moveToFolder", params); - }, - - __moveStudyToWorkspace: function(studyId, workspaceId) { - const params = { - url: { - studyId, - workspaceId, - } - }; - return osparc.data.Resources.fetch("studies", "moveToWorkspace", params); - }, - _updateStudyData: function(studyData) { studyData["resourceType"] = "study"; const studies = this._resourcesList; @@ -1267,8 +1289,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); moveStudyToFolder.addListener("moveToFolder", e => { win.close(); - const folderId = e.getData(); - this.__moveStudyToFolder(studyData["uuid"], folderId) + const destFolderId = e.getData(); + const params = { + url: { + studyId: studyData["uuid"], + folderId: destFolderId, + } + }; + osparc.data.Resources.fetch("studies", "moveToFolder", params) .then(() => { this.__removeFromStudyList(studyData["uuid"]); }) @@ -1288,12 +1316,18 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { moveToWorkspaceButton["moveToWorkspaceButton"] = true; moveToWorkspaceButton.addListener("tap", () => { const title = this.tr("Move") + " " + studyData["name"]; - const moveStudyToWorkspace = new osparc.dashboard.MoveResourceToWorkspace(studyData, this.getCurrentFolderId()); + const moveStudyToWorkspace = new osparc.dashboard.MoveResourceToWorkspace(this.getCurrentFolderId()); const win = osparc.ui.window.Window.popUpInWindow(moveStudyToWorkspace, title, 350, 280); moveStudyToWorkspace.addListener("moveToWorkspace", e => { win.close(); - const workspaceId = e.getData(); - this.__moveStudyToWorkspace(studyData["uuid"], workspaceId) + const destWorkspaceId = e.getData(); + const params = { + url: { + studyId: studyData["uuid"], + workspaceId: destWorkspaceId, + } + }; + osparc.data.Resources.fetch("studies", "moveToWorkspace", params) .then(() => { this.__removeFromStudyList(studyData["uuid"]); }) From 9fcdbad4c52f71f2efa0057e734433572fb7ad5d Mon Sep 17 00:00:00 2001 From: Odei Maiz Date: Fri, 6 Sep 2024 10:18:54 +0200 Subject: [PATCH 58/58] checks --- .../class/osparc/dashboard/StudyBrowser.js | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 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 12406da4328..1e288034fbc 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -173,6 +173,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { const folderId = this.getCurrentFolderId(); const workspaceId = this.getCurrentWorkspaceId(); + if (workspaceId === -1) { + return; + } osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId) .then(folders => { this.__setFoldersToList(folders); @@ -185,6 +188,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (this._loadingResourcesBtn.isFetching()) { return; } + const workspaceId = this.getCurrentWorkspaceId(); + if (workspaceId === -1) { + return; + } osparc.data.Resources.get("tasks") .then(tasks => { @@ -449,6 +456,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { const folderId = this.getCurrentFolderId(); const workspaceId = this.getCurrentWorkspaceId(); + if (workspaceId === -1) { + return; + } osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId) .then(() => { this._resourcesContainer.setResourcesToList([]); @@ -472,16 +482,21 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { newFolderCard.subscribeToFilterGroup("searchBarFilter"); newFolderCard.addListener("createFolder", e => { const data = e.getData(); - const parentFolderId = currentFolder ? currentFolder.getFolderId() : null; - const currentWorkspaceId = this.getCurrentWorkspaceId(); - osparc.store.Folders.getInstance().postFolder(data.name, parentFolderId, currentWorkspaceId) - .then(() => this.__reloadFolders()) - .catch(err => console.error(err)); - }) + this.__createFolder(data); + }, this); this._resourcesContainer.addNewFolderCard(newFolderCard); } }, + __createFolder: function(data) { + const currentFolder = osparc.store.Folders.getInstance().getFolder(this.getCurrentFolderId()) + const parentFolderId = currentFolder ? currentFolder.getFolderId() : null; + const currentWorkspaceId = this.getCurrentWorkspaceId(); + osparc.store.Folders.getInstance().postFolder(data.name, parentFolderId, currentWorkspaceId) + .then(() => this.__reloadFolders()) + .catch(err => console.error(err)); + }, + _folderSelected: function(folderId) { this.setCurrentFolderId(folderId); }, @@ -832,8 +847,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const containerHeader = this._resourcesContainer.getContainerHeader(); if (containerHeader) { - this.bind("currentWorkspaceId", containerHeader, "currentWorkspaceId"); - containerHeader.bind("currentWorkspaceId", this, "currentWorkspaceId"); + this.bind("currentWorkspaceId", containerHeader, "currentWorkspaceId", { + onUpdate: () => containerHeader.setCurrentFolderId(null) + }); + containerHeader.bind("currentWorkspaceId", this, "currentWorkspaceId", { + onUpdate: () => this.setCurrentFolderId(null) + }); this.bind("currentFolderId", containerHeader, "currentFolderId"); containerHeader.bind("currentFolderId", this, "currentFolderId"); }