Skip to content

Commit 08daafc

Browse files
authored
✨ Add ordering to function jobs inside a function job collection (ITISFoundation#8487)
1 parent 95a633d commit 08daafc

File tree

7 files changed

+189
-10
lines changed

7 files changed

+189
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""add order to function job collections
2+
3+
Revision ID: 06596ce2bc3e
4+
Revises: 9dddb16914a4
5+
Create Date: 2025-10-08 13:54:39.943703+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "06596ce2bc3e"
14+
down_revision = "9dddb16914a4"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.add_column(
21+
"funcapi_function_job_collections_to_function_jobs",
22+
sa.Column("order", sa.Integer(), default=0, nullable=True),
23+
)
24+
# Set the "order" column so that it restarts from 1 for each collection_id,
25+
# ordering by function_job_id within each collection
26+
op.execute(
27+
sa.text(
28+
"""
29+
UPDATE funcapi_function_job_collections_to_function_jobs
30+
SET "order" = sub.row_number
31+
FROM (
32+
SELECT function_job_collection_uuid, function_job_uuid,
33+
ROW_NUMBER() OVER (PARTITION BY function_job_collection_uuid) AS row_number
34+
FROM funcapi_function_job_collections_to_function_jobs
35+
) AS sub
36+
WHERE funcapi_function_job_collections_to_function_jobs.function_job_collection_uuid = sub.function_job_collection_uuid
37+
AND funcapi_function_job_collections_to_function_jobs.function_job_uuid = sub.function_job_uuid
38+
"""
39+
)
40+
)
41+
op.drop_constraint(
42+
"funcapi_function_job_collections_to_function_jobs_pk",
43+
"funcapi_function_job_collections_to_function_jobs",
44+
type_="primary",
45+
)
46+
op.create_primary_key(
47+
"funcapi_function_job_collections_to_function_jobs_pk",
48+
"funcapi_function_job_collections_to_function_jobs",
49+
["function_job_collection_uuid", "order"],
50+
)
51+
op.alter_column(
52+
"funcapi_function_job_collections_to_function_jobs",
53+
"order",
54+
existing_type=sa.Integer(),
55+
nullable=False,
56+
)
57+
# ### end Alembic commands ###
58+
59+
60+
def downgrade():
61+
op.drop_constraint(
62+
"funcapi_function_job_collections_to_function_jobs_pk",
63+
"funcapi_function_job_collections_to_function_jobs",
64+
type_="primary",
65+
)
66+
op.create_primary_key(
67+
"funcapi_function_job_collections_to_function_jobs_pk",
68+
"funcapi_function_job_collections_to_function_jobs",
69+
["function_job_collection_uuid", "function_job_uuid"],
70+
)
71+
72+
op.drop_column("funcapi_function_job_collections_to_function_jobs", "order")
73+
# ### end Alembic commands ###
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""merge 06596ce2bc3e a6289977e057
2+
3+
Revision ID: f641b3eacafd
4+
Revises: 06596ce2bc3e, a6289977e057
5+
Create Date: 2025-10-14 07:10:39.664119+00:00
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = "f641b3eacafd"
11+
down_revision = ("06596ce2bc3e", "a6289977e057")
12+
branch_labels = None
13+
depends_on = None
14+
15+
16+
def upgrade():
17+
pass
18+
19+
20+
def downgrade():
21+
pass

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,18 @@
3535
nullable=False,
3636
doc="Unique identifier of the function job",
3737
),
38+
sa.Column(
39+
"order",
40+
sa.Integer,
41+
nullable=False,
42+
doc="Order of the function job in the collection (1-based)",
43+
),
3844
column_created_datetime(),
3945
column_modified_datetime(),
4046
sa.PrimaryKeyConstraint(
4147
"function_job_collection_uuid",
4248
"function_job_uuid",
49+
"order",
4350
name="funcapi_function_job_collections_to_function_jobs_pk",
4451
),
4552
)

services/web/server/src/simcore_service_webserver/functions/_function_job_collections_repository.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,13 @@ async def create_function_job_collection(
9595
row
9696
)
9797
job_collection_entries: list[Row] = []
98-
for job_id in job_ids:
98+
for order, job_id in enumerate(job_ids, 1):
9999
result = await transaction.execute(
100100
function_job_collections_to_function_jobs_table.insert()
101101
.values(
102102
function_job_collection_uuid=function_job_collection_db.uuid,
103103
function_job_uuid=job_id,
104+
order=order,
104105
)
105106
.returning(
106107
function_job_collections_to_function_jobs_table.c.function_job_collection_uuid,
@@ -228,10 +229,15 @@ async def list_function_job_collections(
228229
job_ids = [
229230
job_row.function_job_uuid
230231
async for job_row in await conn.stream(
231-
function_job_collections_to_function_jobs_table.select().where(
232+
function_job_collections_to_function_jobs_table.select()
233+
.where(
232234
function_job_collections_to_function_jobs_table.c.function_job_collection_uuid
233235
== row.uuid
234236
)
237+
.order_by(
238+
function_job_collections_to_function_jobs_table.c.order,
239+
function_job_collections_to_function_jobs_table.c.function_job_uuid,
240+
)
235241
)
236242
]
237243
collections.append((collection, job_ids))
@@ -278,10 +284,15 @@ async def get_function_job_collection(
278284
job_ids = [
279285
job_row.function_job_uuid
280286
async for job_row in await conn.stream(
281-
function_job_collections_to_function_jobs_table.select().where(
287+
function_job_collections_to_function_jobs_table.select()
288+
.where(
282289
function_job_collections_to_function_jobs_table.c.function_job_collection_uuid
283290
== row.uuid
284291
)
292+
.order_by(
293+
function_job_collections_to_function_jobs_table.c.order,
294+
function_job_collections_to_function_jobs_table.c.function_job_uuid,
295+
)
285296
)
286297
]
287298

services/web/server/src/simcore_service_webserver/functions/_function_jobs_repository.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ async def list_function_jobs_with_status(
209209
function_job_collections_to_function_jobs_table.c.function_job_collection_uuid
210210
== filter_by_function_job_collection_id
211211
)
212+
.order_by(
213+
function_job_collections_to_function_jobs_table.c.order,
214+
function_job_collections_to_function_jobs_table.c.function_job_uuid,
215+
)
212216
)
213217
filter_conditions = sqlalchemy.and_(
214218
filter_conditions,

services/web/server/tests/unit/with_dbs/04/functions/wb-api-server/test_function_job_collections_controller_rpc.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,69 @@ async def test_function_job_collection(
159159
)
160160

161161

162+
@pytest.mark.parametrize(
163+
"user_role",
164+
[UserRole.USER],
165+
)
166+
async def test_create_function_job_collection_same_function_job_uuid(
167+
client: TestClient,
168+
add_user_function_api_access_rights: None,
169+
create_fake_function_obj: Callable[[FunctionClass], Function],
170+
webserver_rpc_client: WebServerRpcClient,
171+
logged_user: UserInfoDict,
172+
other_logged_user: UserInfoDict,
173+
user_without_function_api_access_rights: UserInfoDict,
174+
osparc_product_name: ProductName,
175+
):
176+
# Register the function first
177+
registered_function = await webserver_rpc_client.functions.register_function(
178+
function=create_fake_function_obj(FunctionClass.PROJECT),
179+
user_id=logged_user["id"],
180+
product_name=osparc_product_name,
181+
)
182+
assert registered_function.uid is not None
183+
184+
registered_function_job = ProjectFunctionJob(
185+
function_uid=registered_function.uid,
186+
title="Test Function Job",
187+
description="A test function job",
188+
project_job_id=uuid4(),
189+
inputs={"input1": "value1"},
190+
outputs={"output1": "result1"},
191+
job_creation_task_id=None,
192+
)
193+
# Register the function job
194+
function_job_ids = []
195+
registered_function_job = ProjectFunctionJob(
196+
function_uid=registered_function.uid,
197+
title="Test Function Job",
198+
description="A test function job",
199+
project_job_id=uuid4(),
200+
inputs={"input1": "value1"},
201+
outputs={"output1": "result1"},
202+
job_creation_task_id=None,
203+
)
204+
# Register the function job
205+
registered_job = await webserver_rpc_client.functions.register_function_job(
206+
function_job=registered_function_job,
207+
user_id=logged_user["id"],
208+
product_name=osparc_product_name,
209+
)
210+
assert registered_job.uid is not None
211+
212+
function_job_ids = [registered_job.uid] * 3
213+
214+
function_job_collection = FunctionJobCollection(
215+
title="Test Function Job Collection",
216+
description="A test function job collection",
217+
job_ids=function_job_ids,
218+
)
219+
220+
assert function_job_collection.job_ids[0] == registered_job.uid
221+
assert function_job_collection.job_ids[1] == registered_job.uid
222+
assert function_job_collection.job_ids[2] == registered_job.uid
223+
224+
162225
@pytest.mark.parametrize(
163226
"user_role",
164227
[UserRole.USER],

services/web/server/tests/unit/with_dbs/04/functions/wb-api-server/test_function_jobs_controller_rpc.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -325,15 +325,14 @@ async def test_list_function_jobs_filtering(
325325
)
326326
)
327327

328+
job_ids = [
329+
job.uid
330+
for job in first_registered_function_jobs[1:2]
331+
+ second_registered_function_jobs[0:1]
332+
]
328333
function_job_collection = (
329334
await webserver_rpc_client.functions.register_function_job_collection(
330-
function_job_collection=FunctionJobCollection(
331-
job_ids=[
332-
job.uid
333-
for job in first_registered_function_jobs[1:2]
334-
+ second_registered_function_jobs[0:1]
335-
]
336-
),
335+
function_job_collection=FunctionJobCollection(job_ids=job_ids),
337336
user_id=logged_user["id"],
338337
product_name=osparc_product_name,
339338
)
@@ -381,6 +380,7 @@ async def test_list_function_jobs_filtering(
381380

382381
# Assert the list contains the registered job
383382
assert len(jobs) == 2
383+
assert [job.uid for job in jobs] == job_ids
384384
assert jobs[0].uid == first_registered_function_jobs[1].uid
385385
assert jobs[1].uid == second_registered_function_jobs[0].uid
386386

0 commit comments

Comments
 (0)