Skip to content

Commit cb80f55

Browse files
GitHKAndrei Neagu
andauthored
🎨 Allows guest users to run a project form a template with outputs pushing enabled (ITISFoundation#8555)
Co-authored-by: Andrei Neagu <[email protected]>
1 parent 05f8837 commit cb80f55

File tree

6 files changed

+185
-1
lines changed

6 files changed

+185
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""add_projects_extensions
2+
3+
Revision ID: a85557c02d71
4+
Revises: 5756d9282a0a
5+
Create Date: 2025-10-24 15:03:32.846288+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "a85557c02d71"
14+
down_revision = "5756d9282a0a"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table(
22+
"projects_extensions",
23+
sa.Column("project_uuid", sa.String(), nullable=False),
24+
sa.Column(
25+
"allow_guests_to_push_states_and_output_ports",
26+
sa.Boolean(),
27+
server_default=sa.text("false"),
28+
nullable=False,
29+
),
30+
sa.ForeignKeyConstraint(
31+
["project_uuid"],
32+
["projects.uuid"],
33+
name="fk_projects_extensions_project_uuid_projects",
34+
onupdate="CASCADE",
35+
ondelete="CASCADE",
36+
),
37+
sa.PrimaryKeyConstraint("project_uuid"),
38+
)
39+
# ### end Alembic commands ###
40+
41+
42+
def downgrade():
43+
# ### commands auto generated by Alembic - please adjust! ###
44+
op.drop_table("projects_extensions")
45+
# ### end Alembic commands ###
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import sqlalchemy as sa
2+
3+
from ._common import RefActions
4+
from .base import metadata
5+
from .projects import projects
6+
7+
projects_extensions = sa.Table(
8+
"projects_extensions",
9+
metadata,
10+
sa.Column(
11+
"project_uuid",
12+
sa.String,
13+
sa.ForeignKey(
14+
projects.c.uuid,
15+
name="fk_projects_extensions_project_uuid_projects",
16+
ondelete=RefActions.CASCADE,
17+
onupdate=RefActions.CASCADE,
18+
),
19+
primary_key=True,
20+
doc="project reference and primary key for this table",
21+
),
22+
sa.Column(
23+
"allow_guests_to_push_states_and_output_ports",
24+
sa.Boolean(),
25+
nullable=False,
26+
server_default=sa.sql.expression.false(),
27+
doc=(
28+
"When True, guest will save the state of a service "
29+
"and also push the data to the output ports"
30+
),
31+
),
32+
)

‎services/web/server/src/simcore_service_webserver/projects/_projects_repository.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from models_library.workspaces import WorkspaceID
1515
from pydantic import NonNegativeInt, PositiveInt
1616
from simcore_postgres_database.models.projects import projects
17+
from simcore_postgres_database.models.projects_extensions import projects_extensions
1718
from simcore_postgres_database.models.users import users
1819
from simcore_postgres_database.utils_repos import (
1920
get_columns_from_db_model,
@@ -289,3 +290,52 @@ async def delete_project(
289290
if row is None:
290291
raise ProjectNotFoundError(project_uuid=project_uuid)
291292
return ProjectDBGet.model_validate(row)
293+
294+
295+
async def allows_guests_to_push_states_and_output_ports(
296+
app: web.Application,
297+
connection: AsyncConnection | None = None,
298+
*,
299+
project_uuid: str,
300+
) -> bool:
301+
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
302+
result: bool | None = await conn.scalar(
303+
sa.select(
304+
projects_extensions.c.allow_guests_to_push_states_and_output_ports
305+
).where(projects_extensions.c.project_uuid == project_uuid)
306+
)
307+
return result if result is not None else False
308+
309+
310+
async def _set_allow_guests_to_push_states_and_output_ports(
311+
app: web.Application,
312+
connection: AsyncConnection | None = None,
313+
*,
314+
project_uuid: str,
315+
) -> None:
316+
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
317+
await conn.execute(
318+
sa.insert(projects_extensions).values(
319+
project_uuid=project_uuid,
320+
allow_guests_to_push_states_and_output_ports=True,
321+
)
322+
)
323+
324+
325+
async def copy_allow_guests_to_push_states_and_output_ports(
326+
app: web.Application,
327+
connection: AsyncConnection | None = None,
328+
*,
329+
from_project_uuid: str,
330+
to_project_uuid: str,
331+
) -> None:
332+
# get setting from template project
333+
allow_guests = await allows_guests_to_push_states_and_output_ports(
334+
app, connection, project_uuid=from_project_uuid
335+
)
336+
337+
# set same setting in new project if True
338+
if allow_guests:
339+
await _set_allow_guests_to_push_states_and_output_ports(
340+
app, connection, project_uuid=to_project_uuid
341+
)

‎services/web/server/src/simcore_service_webserver/projects/_projects_service.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,13 @@ async def _start_dynamic_service( # pylint: disable=too-many-statements # noqa
817817
save_state = await has_user_project_access_rights(
818818
request.app, project_id=project_uuid, user_id=user_id, permission="write"
819819
)
820+
if (
821+
user_role == UserRole.GUEST
822+
and await _projects_repository.allows_guests_to_push_states_and_output_ports(
823+
request.app, project_uuid=f"{project_uuid}"
824+
)
825+
):
826+
save_state = True
820827

821828
@exclusive(
822829
get_redis_lock_manager_client_sdk(request.app),

‎services/web/server/src/simcore_service_webserver/studies_dispatcher/_studies_access.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ..director_v2 import director_v2_service
3131
from ..dynamic_scheduler import api as dynamic_scheduler_service
3232
from ..products import products_web
33+
from ..projects import _projects_repository
3334
from ..projects._groups_repository import get_project_group
3435
from ..projects._projects_repository_legacy import ProjectDBAPI
3536
from ..projects.api import check_user_project_permission
@@ -221,6 +222,12 @@ async def copy_study_to_account(
221222
request.app, project_id=ProjectID(project["uuid"])
222223
)
223224

225+
await _projects_repository.copy_allow_guests_to_push_states_and_output_ports(
226+
request.app,
227+
from_project_uuid=template_project["uuid"],
228+
to_project_uuid=project["uuid"],
229+
)
230+
224231
return project_uuid
225232

226233

‎services/web/server/tests/unit/with_dbs/02/test_projects_repository.py‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
# pylint: disable=protected-access
12
# pylint: disable=redefined-outer-name
3+
# pylint: disable=too-many-arguments
24
# pylint: disable=unused-argument
35
# pylint: disable=unused-variable
4-
# pylint: disable=too-many-arguments
56

67
from datetime import datetime, timedelta
78
from uuid import UUID
89

910
import arrow
1011
import pytest
12+
from aiohttp import web
1113
from aiohttp.test_utils import TestClient
1214
from common_library.users_enums import UserRole
1315
from models_library.basic_types import IDStr
@@ -195,3 +197,44 @@ async def test_batch_get_trashed_by_primary_gid(
195197
None,
196198
logged_user["primary_gid"],
197199
]
200+
201+
202+
async def _assert_allows_to_psuh(
203+
app: web.Application, project_uuid: str, *, expected: bool
204+
) -> None:
205+
result = (
206+
await projects_service_repository.allows_guests_to_push_states_and_output_ports(
207+
app, project_uuid=project_uuid
208+
)
209+
)
210+
assert result is expected
211+
212+
213+
async def test_guest_allowed_to_push(
214+
client: TestClient, user_project: ProjectDict, shared_project: ProjectDict
215+
):
216+
assert client.app
217+
218+
source_uuid = user_project["uuid"]
219+
copy_uuid = shared_project["uuid"]
220+
221+
await _assert_allows_to_psuh(client.app, source_uuid, expected=False)
222+
223+
# add the entry in the table
224+
await projects_service_repository._set_allow_guests_to_push_states_and_output_ports( # noqa: SLF001
225+
client.app, project_uuid=source_uuid
226+
)
227+
228+
await _assert_allows_to_psuh(client.app, source_uuid, expected=True)
229+
230+
assert (
231+
await projects_service_repository.allows_guests_to_push_states_and_output_ports(
232+
client.app, project_uuid=copy_uuid
233+
)
234+
is False
235+
)
236+
await _assert_allows_to_psuh(client.app, copy_uuid, expected=False)
237+
await projects_service_repository.copy_allow_guests_to_push_states_and_output_ports(
238+
client.app, from_project_uuid=source_uuid, to_project_uuid=copy_uuid
239+
)
240+
await _assert_allows_to_psuh(client.app, copy_uuid, expected=True)

0 commit comments

Comments
 (0)