Skip to content

Commit

Permalink
🐛 Project list items include permalink (#4214)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored May 11, 2023
1 parent 5b4b9b3 commit d9fd2da
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ def test_get_dynamic_proxy_spec(
# TODO: finish test when working on https://github.com/ITISFoundation/osparc-simcore/issues/2454


@pytest.mark.testit
async def test_merge_dynamic_sidecar_specs_with_user_specific_specs(
mocked_catalog_service_api: respx.MockRouter,
minimal_app: FastAPI,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ qx.Class.define("osparc.data.model.Study", {
tags: studyData.tags || this.getTags(),
state: studyData.state || this.getState(),
quality: studyData.quality || this.getQuality(),
permalink: studyData.permalink || this.getPermalink(),
dev: studyData.dev || this.getDev()
});

Expand Down Expand Up @@ -156,6 +157,12 @@ qx.Class.define("osparc.data.model.Study", {
nullable: true
},

permalink: {
check: "Object",
nullable: true,
init: {}
},

dev: {
check: "Object",
nullable: true,
Expand Down Expand Up @@ -188,6 +195,7 @@ qx.Class.define("osparc.data.model.Study", {

statics: {
IgnoreSerializationProps: [
"permalink",
"state",
"pipelineRunning",
"readOnly"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
""" Utils to implement READ operations (from cRud) on the project resource
Read operations are list, get
"""
from aiohttp import web
from models_library.projects import ProjectID
from models_library.users import UserID
from pydantic import NonNegativeInt
from servicelib.utils import logged_gather
from simcore_postgres_database.webserver_models import ProjectType as ProjectTypeDB
from simcore_service_webserver.rest_schemas_base import OutputSchema

from .. import catalog
from . import projects_api
from ._permalink import update_or_pop_permalink_in_project
from ._rest_schemas import ProjectListItem
from .project_models import ProjectDict, ProjectTypeAPI
from .projects_db import ProjectDBAPI


async def _append_fields(
request: web.Request,
user_id: UserID,
project: ProjectDict,
is_template: bool,
model_schema_cls: type[OutputSchema],
):
# state
await projects_api.add_project_states_for_user(
user_id=user_id,
project=project,
is_template=is_template,
app=request.app,
)

# permalink
await update_or_pop_permalink_in_project(request, project)

# validate
project_data = model_schema_cls.parse_obj(project).data(exclude_unset=True)
return project_data


async def list_projects(
request: web.Request,
user_id: UserID,
product_name: str,
project_type: ProjectTypeAPI,
show_hidden: bool,
offset: NonNegativeInt,
limit: int,
) -> tuple[list[ProjectDict], int]:

app = request.app
db = ProjectDBAPI.get_from_app_context(app)

user_available_services: list[
dict
] = await catalog.get_services_for_user_in_product(
app, user_id, product_name, only_key_versions=True
)

db_projects, db_project_types, total_number_projects = await db.list_projects(
user_id=user_id,
product_name=product_name,
filter_by_project_type=ProjectTypeAPI.to_project_type_db(project_type),
filter_by_services=user_available_services,
offset=offset,
limit=limit,
include_hidden=show_hidden,
)

projects: list[ProjectDict] = await logged_gather(
*(
_append_fields(
request,
user_id,
project=prj,
is_template=prj_type == ProjectTypeDB.TEMPLATE,
model_schema_cls=ProjectListItem,
)
for prj, prj_type in zip(db_projects, db_project_types)
),
reraise=True,
max_concurrency=100,
)

return projects, total_number_projects


async def get_project(
request: web.Request,
user_id: UserID,
product_name: str,
project_uuid: ProjectID,
project_type: ProjectTypeAPI,
):
raise NotImplementedError()
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""
import json
import logging
from typing import Any

from aiohttp import web
from jsonschema import ValidationError as JsonSchemaValidationError
Expand All @@ -29,8 +28,6 @@
from servicelib.json_serialization import json_dumps
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
from servicelib.utils import logged_gather
from simcore_postgres_database.webserver_models import ProjectType as ProjectTypeDB

from .. import catalog
from .._constants import RQ_PRODUCT_KEY
Expand All @@ -41,7 +38,7 @@
from ..security.api import check_permission
from ..security.decorators import permission_required
from ..users_api import get_user_name
from . import _create_utils, projects_api
from . import _create_utils, _read_utils, projects_api
from ._permalink import update_or_pop_permalink_in_project
from ._rest_schemas import (
EmptyModel,
Expand Down Expand Up @@ -203,51 +200,24 @@ async def list_projects(request: web.Request):
web.HTTPUnprocessableEntity: (422) if validation of request parameters fail
"""

db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(request.app)
req_ctx = RequestContext.parse_obj(request)
query_params = parse_request_query_parameters_as(_ProjectListParams, request)

async def set_all_project_states(
projects: list[dict[str, Any]], project_types: list[ProjectTypeDB]
):
await logged_gather(
*[
projects_api.add_project_states_for_user(
user_id=req_ctx.user_id,
project=prj,
is_template=prj_type == ProjectTypeDB.TEMPLATE,
app=request.app,
)
for prj, prj_type in zip(projects, project_types)
],
reraise=True,
max_concurrency=100,
)

user_available_services: list[
dict
] = await catalog.get_services_for_user_in_product(
request.app, req_ctx.user_id, req_ctx.product_name, only_key_versions=True
)

projects, project_types, total_number_projects = await db.list_projects(
projects, total_number_of_projects = await _read_utils.list_projects(
request,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
filter_by_project_type=ProjectTypeAPI.to_project_type_db(
query_params.project_type
),
filter_by_services=user_available_services,
offset=query_params.offset,
project_type=query_params.project_type,
show_hidden=query_params.show_hidden,
limit=query_params.limit,
include_hidden=query_params.show_hidden,
offset=query_params.offset,
)
await set_all_project_states(projects, project_types)

page = Page[ProjectDict].parse_obj(
paginate_data(
chunk=projects,
request_url=request.url,
total=total_number_projects,
total=total_number_of_projects,
limit=query_params.limit,
offset=query_params.offset,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,14 @@ async def _assert_get_same_project(
async def _replace_project(
client: TestClient, project_update: dict, expected: type[web.HTTPException]
) -> dict:
assert client.app

# PUT /v0/projects/{project_id}
url = client.app.router["replace_project"].url_for(
project_id=project_update["uuid"]
)
assert str(url) == f"{API_PREFIX}/projects/{project_update['uuid']}"
resp = await client.put(url, json=project_update)
resp = await client.put(f"{url}", json=project_update)
data, error = await assert_status(resp, expected)
if not error:
assert_replaced(current_project=data, update_data=project_update)
Expand Down Expand Up @@ -194,36 +196,54 @@ async def test_list_projects(
if data:
assert len(data) == 2

# template project
project_state = data[0].pop("state")
project_permalink = data[0].pop("permalink")

assert data[0] == template_project
assert not ProjectState(
**project_state
).locked.value, "Templates are not locked"
assert parse_obj_as(ProjectPermalink, project_permalink)

# standard project
project_state = data[1].pop("state")
project_permalink = data[1].pop("permalink", None)

assert data[1] == user_project
assert ProjectState(**project_state)
assert project_permalink is None

# GET /v0/projects?type=user
data, *_ = await _list_projects(client, expected, {"type": "user"})
if data:
assert len(data) == 1

# standad project
project_state = data[0].pop("state")
project_permalink = data[0].pop("permalink", None)

assert data[0] == user_project
assert not ProjectState(
**project_state
).locked.value, "Single user does not lock"
assert project_permalink is None

# GET /v0/projects?type=template
# instead /v0/projects/templates ??
data, *_ = await _list_projects(client, expected, {"type": "template"})
if data:
assert len(data) == 1

# template project
project_state = data[0].pop("state")
project_permalink = data[0].pop("permalink")

assert data[0] == template_project
assert not ProjectState(
**project_state
).locked.value, "Templates are not locked"
assert parse_obj_as(ProjectPermalink, project_permalink)


@pytest.fixture(scope="session")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ async def context_with_logged_user(client: TestClient, logged_user: UserInfoDict
await conn.execute(projects.delete())


@pytest.mark.testit
@pytest.mark.acceptance_test
async def test_iterators_workflow(
client: TestClient,
Expand Down

0 comments on commit d9fd2da

Please sign in to comment.