Skip to content

Commit 7e1ccbd

Browse files
✨ add project tags to RUT listing/export (#6722)
1 parent d3e12a3 commit 7e1ccbd

File tree

11 files changed

+157
-10
lines changed

11 files changed

+157
-10
lines changed

packages/models-library/src/models_library/api_schemas_resource_usage_tracker/service_runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ServiceRunGet(BaseModel):
2020
user_email: str
2121
project_id: ProjectID
2222
project_name: str
23+
project_tags: list[str]
2324
node_id: NodeID
2425
node_name: str
2526
root_parent_project_id: ProjectID
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""enhance projects_tags for RUT
2+
3+
Revision ID: 8e1f83486be7
4+
Revises: 8bfe65a5e294
5+
Create Date: 2024-11-15 09:12:57.789183+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "8e1f83486be7"
13+
down_revision = "8bfe65a5e294"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.add_column(
21+
"projects_tags", sa.Column("project_uuid_for_rut", sa.String(), nullable=True)
22+
)
23+
24+
# Migrate
25+
op.execute(
26+
sa.DDL(
27+
"""
28+
UPDATE projects_tags
29+
SET project_uuid_for_rut = projects.uuid
30+
FROM projects
31+
WHERE projects_tags.project_id = projects.id;
32+
"""
33+
)
34+
)
35+
36+
op.alter_column(
37+
"projects_tags",
38+
"project_uuid_for_rut",
39+
existing_type=sa.String(),
40+
nullable=False,
41+
)
42+
op.alter_column(
43+
"projects_tags", "project_id", existing_type=sa.BIGINT(), nullable=True
44+
)
45+
op.drop_constraint(
46+
"study_tags_study_id_tag_id_key", "projects_tags", type_="unique"
47+
)
48+
op.create_unique_constraint(
49+
"project_tags_project_uuid_unique",
50+
"projects_tags",
51+
["project_uuid_for_rut", "tag_id"],
52+
)
53+
op.drop_constraint("study_tags_study_id_fkey", "projects_tags", type_="foreignkey")
54+
op.create_foreign_key(
55+
"project_tags_project_id_fkey",
56+
"projects_tags",
57+
"projects",
58+
["project_id"],
59+
["id"],
60+
onupdate="CASCADE",
61+
ondelete="SET NULL",
62+
)
63+
# ### end Alembic commands ###
64+
65+
66+
def downgrade():
67+
# ### commands auto generated by Alembic - please adjust! ###
68+
op.drop_constraint(
69+
"project_tags_project_id_fkey", "projects_tags", type_="foreignkey"
70+
)
71+
op.create_foreign_key(
72+
"study_tags_study_id_fkey",
73+
"projects_tags",
74+
"projects",
75+
["project_id"],
76+
["id"],
77+
onupdate="CASCADE",
78+
ondelete="CASCADE",
79+
)
80+
op.drop_constraint(
81+
"project_tags_project_uuid_unique", "projects_tags", type_="unique"
82+
)
83+
op.create_unique_constraint(
84+
"study_tags_study_id_tag_id_key", "projects_tags", ["project_id", "tag_id"]
85+
)
86+
op.alter_column(
87+
"projects_tags", "project_id", existing_type=sa.BIGINT(), nullable=False
88+
)
89+
op.drop_column("projects_tags", "project_uuid_for_rut")
90+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/projects_tags.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@
1313
sa.Column(
1414
"project_id",
1515
sa.BigInteger,
16-
sa.ForeignKey(projects.c.id, onupdate="CASCADE", ondelete="CASCADE"),
17-
nullable=False,
18-
doc="NOTE that project.c.id != project.c.uuid",
16+
sa.ForeignKey(
17+
projects.c.id,
18+
onupdate="CASCADE",
19+
ondelete="SET NULL",
20+
name="project_tags_project_id_fkey",
21+
),
22+
nullable=True, # <-- NULL means that project was deleted
23+
doc="NOTE that project.c.id != project.c.uuid. If project is deleted, we do not delete project in this table, we just set this column to NULL. Why? Because the `project_uuid_for_rut` is still used by resource usage tracker",
1924
),
2025
sa.Column(
2126
"tag_id",
2227
sa.BigInteger,
2328
sa.ForeignKey(tags.c.id, onupdate="CASCADE", ondelete="CASCADE"),
2429
nullable=False,
2530
),
26-
sa.UniqueConstraint("project_id", "tag_id"),
31+
sa.Column(
32+
"project_uuid_for_rut",
33+
sa.String,
34+
nullable=False,
35+
),
36+
sa.UniqueConstraint(
37+
"project_uuid_for_rut", "tag_id", name="project_tags_project_uuid_unique"
38+
),
2739
)

packages/postgres-database/src/simcore_postgres_database/utils_tags_sql.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
from uuid import UUID
23

34
import sqlalchemy as sa
45
from simcore_postgres_database.models.groups import user_to_groups
@@ -60,7 +61,7 @@ def get_tag_stmt(
6061
# aggregation ensures MOST PERMISSIVE policy of access-rights
6162
sa.func.bool_or(tags_access_rights.c.read).label("read"),
6263
sa.func.bool_or(tags_access_rights.c.write).label("write"),
63-
sa.func.bool_or(tags_access_rights.c.delete).label("delete")
64+
sa.func.bool_or(tags_access_rights.c.delete).label("delete"),
6465
)
6566
.select_from(
6667
_join_user_to_given_tag(
@@ -80,7 +81,7 @@ def list_tags_stmt(*, user_id: int):
8081
# aggregation ensures MOST PERMISSIVE policy of access-rights
8182
sa.func.bool_or(tags_access_rights.c.read).label("read"),
8283
sa.func.bool_or(tags_access_rights.c.write).label("write"),
83-
sa.func.bool_or(tags_access_rights.c.delete).label("delete")
84+
sa.func.bool_or(tags_access_rights.c.delete).label("delete"),
8485
)
8586
.select_from(
8687
_join_user_to_tags(
@@ -104,7 +105,7 @@ def count_groups_with_given_access_rights_stmt(
104105
tag_id: int,
105106
read: bool | None,
106107
write: bool | None,
107-
delete: bool | None
108+
delete: bool | None,
108109
):
109110
"""
110111
How many groups (from this user_id) are given EXACTLY these access permissions
@@ -192,12 +193,15 @@ def get_tags_for_project_stmt(*, project_index: int):
192193
)
193194

194195

195-
def add_tag_to_project_stmt(*, project_index: int, tag_id: int):
196+
def add_tag_to_project_stmt(
197+
*, project_index: int, tag_id: int, project_uuid_for_rut: UUID
198+
):
196199
return (
197200
pg_insert(projects_tags)
198201
.values(
199202
project_id=project_index,
200203
tag_id=tag_id,
204+
project_uuid_for_rut=f"{project_uuid_for_rut}",
201205
)
202206
.on_conflict_do_nothing()
203207
)

packages/postgres-database/tests/test_utils_tags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ def _check(func_smt, **kwargs):
668668
user_id = 425 # 4
669669
tag_id = 4
670670
project_index = 1
671+
project_uuid = "106f8b4b-ffb6-459a-a27b-981c779e6d3f"
671672
service_key = "simcore/services/comp/isolve"
672673
service_version = "2.0.85"
673674

@@ -726,6 +727,7 @@ def _check(func_smt, **kwargs):
726727
add_tag_to_project_stmt,
727728
project_index=project_index,
728729
tag_id=tag_id,
730+
project_uuid_for_rut=project_uuid,
729731
)
730732

731733
_check(

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/service_runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class Config:
101101
class ServiceRunWithCreditsDB(ServiceRunDB):
102102
osparc_credits: Decimal | None
103103
transaction_status: CreditTransactionStatus | None
104+
project_tags: list[str]
104105

105106
class Config:
106107
orm_mode = True

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/repositories/resource_tracker.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from models_library.users import UserID
2929
from models_library.wallets import WalletID
3030
from pydantic import PositiveInt
31+
from simcore_postgres_database.models.projects_tags import projects_tags
3132
from simcore_postgres_database.models.resource_tracker_credit_transactions import (
3233
resource_tracker_credit_transactions,
3334
)
@@ -46,6 +47,7 @@
4647
from simcore_postgres_database.models.resource_tracker_service_runs import (
4748
resource_tracker_service_runs,
4849
)
50+
from simcore_postgres_database.models.tags import tags
4951
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER
5052

5153
from .....exceptions.errors import (
@@ -212,6 +214,15 @@ async def get_service_run_by_id(
212214
return None
213215
return ServiceRunDB.from_orm(row)
214216

217+
_project_tags_subquery = (
218+
sa.select(
219+
projects_tags.c.project_uuid_for_rut,
220+
sa.func.array_agg(tags.c.name).label("project_tags"),
221+
)
222+
.select_from(projects_tags.join(tags, projects_tags.c.tag_id == tags.c.id))
223+
.group_by(projects_tags.c.project_uuid_for_rut)
224+
).subquery("project_tags_subquery")
225+
215226
async def list_service_runs_by_product_and_user_and_wallet(
216227
self,
217228
product_name: ProductName,
@@ -260,6 +271,10 @@ async def list_service_runs_by_product_and_user_and_wallet(
260271
resource_tracker_service_runs.c.missed_heartbeat_counter,
261272
resource_tracker_credit_transactions.c.osparc_credits,
262273
resource_tracker_credit_transactions.c.transaction_status,
274+
sa.func.coalesce(
275+
self._project_tags_subquery.c.project_tags,
276+
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.String)),
277+
).label("project_tags"),
263278
)
264279
.select_from(
265280
resource_tracker_service_runs.join(
@@ -273,6 +288,11 @@ async def list_service_runs_by_product_and_user_and_wallet(
273288
== resource_tracker_credit_transactions.c.service_run_id
274289
),
275290
isouter=True,
291+
).join(
292+
self._project_tags_subquery,
293+
resource_tracker_service_runs.c.project_id
294+
== self._project_tags_subquery.c.project_uuid_for_rut,
295+
isouter=True,
276296
)
277297
)
278298
.where(resource_tracker_service_runs.c.product_name == product_name)
@@ -436,7 +456,9 @@ async def export_service_runs_table_to_s3(
436456
resource_tracker_service_runs.c.service_run_id,
437457
resource_tracker_service_runs.c.wallet_name,
438458
resource_tracker_service_runs.c.user_email,
439-
resource_tracker_service_runs.c.project_name,
459+
resource_tracker_service_runs.c.root_parent_project_name.label(
460+
"project_name"
461+
),
440462
resource_tracker_service_runs.c.node_name,
441463
resource_tracker_service_runs.c.service_key,
442464
resource_tracker_service_runs.c.service_version,
@@ -445,13 +467,22 @@ async def export_service_runs_table_to_s3(
445467
resource_tracker_service_runs.c.stopped_at,
446468
resource_tracker_credit_transactions.c.osparc_credits,
447469
resource_tracker_credit_transactions.c.transaction_status,
470+
sa.func.coalesce(
471+
self._project_tags_subquery.c.project_tags,
472+
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.String)),
473+
).label("project_tags"),
448474
)
449475
.select_from(
450476
resource_tracker_service_runs.join(
451477
resource_tracker_credit_transactions,
452478
resource_tracker_service_runs.c.service_run_id
453479
== resource_tracker_credit_transactions.c.service_run_id,
454480
isouter=True,
481+
).join(
482+
self._project_tags_subquery,
483+
resource_tracker_service_runs.c.project_id
484+
== self._project_tags_subquery.c.project_uuid_for_rut,
485+
isouter=True,
455486
)
456487
)
457488
.where(resource_tracker_service_runs.c.product_name == product_name)

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ async def list_service_runs(
121121
user_email=service.user_email,
122122
project_id=service.project_id,
123123
project_name=service.project_name,
124+
project_tags=service.project_tags,
124125
root_parent_project_id=service.root_parent_project_id,
125126
root_parent_project_name=service.root_parent_project_name,
126127
node_id=service.node_id,

services/resource-usage-tracker/tests/unit/with_dbs/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def mock_env(monkeypatch: pytest.MonkeyPatch) -> EnvVarsDict:
5151
"SC_BOOT_MODE": "production",
5252
"POSTGRES_CLIENT_NAME": "postgres_test_client",
5353
"RESOURCE_USAGE_TRACKER_MISSED_HEARTBEAT_CHECK_ENABLED": "0",
54+
"RESOURCE_USAGE_TRACKER_TRACING": "null",
5455
}
5556
setenvs_from_dict(monkeypatch, env_vars)
5657
return env_vars

services/web/server/src/simcore_service_webserver/projects/db.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
403403
sa.select(
404404
projects_tags.c.project_id,
405405
sa.func.array_agg(projects_tags.c.tag_id).label("tags"),
406-
).group_by(projects_tags.c.project_id)
406+
)
407+
.where(projects_tags.c.project_id.is_not(None))
408+
.group_by(projects_tags.c.project_id)
407409
).subquery("project_tags_subquery")
408410

409411
###
@@ -1218,6 +1220,7 @@ async def add_tag(
12181220
projects_tags.insert().values(
12191221
project_id=project["id"],
12201222
tag_id=tag_id,
1223+
project_uuid_for_rut=project["uuid"],
12211224
)
12221225
)
12231226
project_tags.append(tag_id)

0 commit comments

Comments
 (0)