@@ -382,80 +382,113 @@ async def one(db: Connection, project_id: uuid.UUID):
382
382
project_record .task_count = len (task_records )
383
383
return project_record
384
384
385
+
385
386
async def all (
386
387
db : Connection ,
387
388
user_id : Optional [str ] = None ,
388
389
search : Optional [str ] = None ,
390
+ status : Optional [ProjectCompletionStatus ] = None ,
389
391
skip : int = 0 ,
390
392
limit : int = 100 ,
391
393
):
392
394
"""
393
395
Get all projects, count total tasks and task states (ongoing, completed, etc.).
394
- Optionally filter by the project creator (user) and search by project name.
396
+ Optionally filter by the project creator (user), search by project name, and status .
395
397
"""
396
398
search_term = f"%{ search } %" if search else "%"
397
- async with db .cursor (row_factory = dict_row ) as cur :
398
- await cur .execute (
399
- """
400
- SELECT
401
- p.id, p.slug, p.name, p.description, p.per_task_instructions, p.created_at, p.author_id,
402
- ST_AsGeoJSON(p.outline)::jsonb AS outline,
403
- p.requires_approval_from_manager_for_locking,
399
+ status_value = status .value if status else None
404
400
405
- -- Count total tasks for each project
406
- COUNT(t.id) AS total_task_count,
407
-
408
- -- Count based on the latest state of tasks
409
- COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) AS ongoing_task_count,
410
-
411
- -- Count based on the latest state of tasks
412
- COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) AS completed_task_count
413
-
414
- FROM projects p
415
- LEFT JOIN tasks t ON t.project_id = p.id
416
- LEFT JOIN (
417
- -- Get the latest event per task
418
- SELECT DISTINCT ON (te.task_id)
419
- te.task_id,
420
- te.state,
421
- te.created_at
422
- FROM task_events te
423
- ORDER BY te.task_id, te.created_at DESC
424
- ) AS te ON te.task_id = t.id
425
-
426
- WHERE (p.author_id = COALESCE(%(user_id)s, p.author_id))
427
- AND p.name ILIKE %(search)s
428
-
429
- -- Uncomment this if we want to restrict projects before local regulation accepts it
430
- -- AND (
431
- -- %(user_id)s IS NOT NULL
432
- -- OR p.requires_approval_from_regulator = 'f'
433
- -- OR p.regulator_approval_status = 'APPROVED'
434
- -- )
435
-
436
- GROUP BY p.id
437
- ORDER BY p.created_at DESC
401
+ async with db .cursor (row_factory = dict_row ) as cur :
402
+ query = """
403
+ WITH project_stats AS (
404
+ SELECT
405
+ p.id,
406
+ p.slug,
407
+ p.name,
408
+ p.description,
409
+ p.per_task_instructions,
410
+ p.created_at,
411
+ p.author_id,
412
+ ST_AsGeoJSON(p.outline)::jsonb AS outline,
413
+ p.requires_approval_from_manager_for_locking,
414
+ COUNT(t.id) AS total_task_count,
415
+ COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) AS ongoing_task_count,
416
+ COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) AS completed_task_count,
417
+ CASE
418
+ WHEN COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) = COUNT(t.id) THEN 'completed'
419
+ WHEN COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) = 0
420
+ AND COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) = 0 THEN 'not-started'
421
+ ELSE 'ongoing'
422
+ END AS calculated_status
423
+ FROM projects p
424
+ LEFT JOIN tasks t ON t.project_id = p.id
425
+ LEFT JOIN (
426
+ SELECT DISTINCT ON (te.task_id)
427
+ te.task_id,
428
+ te.state,
429
+ te.created_at
430
+ FROM task_events te
431
+ ORDER BY te.task_id, te.created_at DESC
432
+ ) AS te ON te.task_id = t.id
433
+ WHERE (p.author_id = COALESCE(%(user_id)s, p.author_id))
434
+ AND p.name ILIKE %(search)s
435
+ GROUP BY p.id, p.slug, p.name, p.description, p.per_task_instructions, p.created_at, p.author_id, p.outline, p.requires_approval_from_manager_for_locking
436
+ )
437
+ SELECT *
438
+ FROM project_stats
439
+ WHERE CAST(%(status)s AS text) IS NULL
440
+ OR calculated_status = CAST(%(status)s AS text)
441
+ ORDER BY created_at DESC
438
442
OFFSET %(skip)s
439
443
LIMIT %(limit)s
440
- """ ,
444
+ """
445
+
446
+ await cur .execute (
447
+ query ,
441
448
{
442
449
"skip" : skip ,
443
450
"limit" : limit ,
444
451
"user_id" : user_id ,
445
452
"search" : search_term ,
453
+ "status" : status_value ,
446
454
},
447
455
)
448
456
db_projects = await cur .fetchall ()
449
457
450
458
async with db .cursor () as cur :
459
+ count_query = """
460
+ WITH project_stats AS (
461
+ SELECT
462
+ p.id,
463
+ CASE
464
+ WHEN COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) = COUNT(t.id) THEN 'completed'
465
+ WHEN COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) = 0
466
+ AND COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) = 0 THEN 'not-started'
467
+ ELSE 'ongoing'
468
+ END AS calculated_status
469
+ FROM projects p
470
+ LEFT JOIN tasks t ON t.project_id = p.id
471
+ LEFT JOIN (
472
+ SELECT DISTINCT ON (te.task_id)
473
+ te.task_id,
474
+ te.state,
475
+ te.created_at
476
+ FROM task_events te
477
+ ORDER BY te.task_id, te.created_at DESC
478
+ ) AS te ON te.task_id = t.id
479
+ WHERE (p.author_id = COALESCE(%(user_id)s, p.author_id))
480
+ AND p.name ILIKE %(search)s
481
+ GROUP BY p.id
482
+ )
483
+ SELECT COUNT(*)
484
+ FROM project_stats
485
+ WHERE CAST(%(status)s AS text) IS NULL
486
+ OR calculated_status = CAST(%(status)s AS text)
487
+ """
451
488
await cur .execute (
452
- """
453
- SELECT COUNT(*) FROM projects p
454
- WHERE (p.author_id = COALESCE(%(user_id)s, p.author_id))
455
- AND p.name ILIKE %(search)s""" ,
456
- {"user_id" : user_id , "search" : search_term },
489
+ count_query ,
490
+ {"user_id" : user_id , "search" : search_term , "status" : status_value },
457
491
)
458
-
459
492
total_count = await cur .fetchone ()
460
493
461
494
return db_projects , total_count [0 ]
0 commit comments