Skip to content

Commit 7c88308

Browse files
illusionaldancoatesjmarshall
authored
OurDNA: Improve project table view and filtering (#742)
* Upgrade vite * Add better filter options for participant * Start to break apart project grids * Stepping closer * Move project summary to projectoverview * More refactoring + filtering * Bunch of small and large changes * Quick and dirty project export button * Export button + small updates * Add startswith + fix tests + implement more + add :tired: * Move ProjectGrid state into URL * Minor fixes * YAML support + code editor * Move a bunch of stuff * More code fixes + linting * Fix created_on * Improve ProjectGrid * Previous commit I forgot? * Linting * Integrate external_ids more concretely * Come back to failing test later * Gotcha! * Revert a previous change + linting * Apply changes, fix the rest later * Rename to filters * Continue renaming filters * Rewrite the projectparticipantgrid fields * Linting * Add re-orderable columns * Apply review feedback * Make the type machine happy * Improve value filter rendering in participant grid (#834) * tmp * Linting --------- Co-authored-by: Dan Coates <[email protected]> Co-authored-by: Michael Franklin <[email protected]> * Apply review feedback + standardise from_db_json * Read the code before you make changes * Update db/python/tables/participant.py Co-authored-by: John Marshall <[email protected]> --------- Co-authored-by: Michael Franklin <[email protected]> Co-authored-by: Dan Coates <[email protected]> Co-authored-by: John Marshall <[email protected]>
1 parent 8124a13 commit 7c88308

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+6086
-2722
lines changed

api/graphql/filters.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import strawberry
44

5-
from db.python.utils import GenericFilter, GenericMetaFilter
5+
from db.python.filters import GenericFilter, GenericMetaFilter
66

77
T = TypeVar('T')
88
Y = TypeVar('Y')
@@ -21,6 +21,7 @@ class GraphQLFilter(Generic[T]):
2121
lte: T | None = None
2222
contains: T | None = None
2323
icontains: T | None = None
24+
startswith: T | None = None
2425

2526
def all_values(self):
2627
"""
@@ -45,6 +46,8 @@ def all_values(self):
4546
v.append(self.contains)
4647
if self.icontains:
4748
v.append(self.icontains)
49+
if self.startswith:
50+
v.append(self.startswith)
4851

4952
return v
5053

@@ -60,6 +63,7 @@ def to_internal_filter(self) -> GenericFilter[T]:
6063
lte=self.lte,
6164
contains=self.contains,
6265
icontains=self.icontains,
66+
startswith=self.startswith,
6367
)
6468

6569
def to_internal_filter_mapped(self, f: Callable[[T], Y]) -> GenericFilter[Y]:
@@ -77,6 +81,7 @@ def to_internal_filter_mapped(self, f: Callable[[T], Y]) -> GenericFilter[Y]:
7781
lte=f(self.lte) if self.lte else None,
7882
contains=f(self.contains) if self.contains else None,
7983
icontains=f(self.icontains) if self.icontains else None,
84+
startswith=f(self.startswith) if self.startswith else None,
8085
)
8186

8287

api/graphql/loaders.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from strawberry.dataloader import DataLoader
1111

1212
from api.utils import get_projectless_db_connection, group_by
13+
from db.python.filters import GenericFilter, get_hashable_value
1314
from db.python.layers import (
1415
AnalysisLayer,
1516
AssayLayer,
@@ -22,10 +23,11 @@
2223
from db.python.tables.analysis import AnalysisFilter
2324
from db.python.tables.assay import AssayFilter
2425
from db.python.tables.family import FamilyFilter
26+
from db.python.tables.participant import ParticipantFilter
2527
from db.python.tables.project import ProjectPermissionsTable
2628
from db.python.tables.sample import SampleFilter
2729
from db.python.tables.sequencing_group import SequencingGroupFilter
28-
from db.python.utils import GenericFilter, NotFoundError, get_hashable_value
30+
from db.python.utils import NotFoundError
2931
from models.models import (
3032
AnalysisInternal,
3133
AssayInternal,
@@ -254,7 +256,12 @@ async def load_sequencing_groups_for_samples(
254256
"""
255257
sglayer = SequencingGroupLayer(connection)
256258
_filter = dataclasses.replace(filter) if filter else SequencingGroupFilter()
257-
_filter.sample_id = GenericFilter(in_=ids)
259+
if not _filter.sample:
260+
_filter.sample = SequencingGroupFilter.SequencingGroupSampleFilter(
261+
id=GenericFilter(in_=ids)
262+
)
263+
else:
264+
_filter.sample.id = GenericFilter(in_=ids)
258265

259266
sequencing_groups = await sglayer.query(_filter)
260267
sg_map = group_by(sequencing_groups, lambda sg: sg.sample_id)
@@ -380,22 +387,22 @@ async def load_participants_for_families(
380387
return [pmap.get(fid, []) for fid in family_ids]
381388

382389

383-
@connected_data_loader(LoaderKeys.PARTICIPANTS_FOR_PROJECTS)
390+
@connected_data_loader_with_params(
391+
LoaderKeys.PARTICIPANTS_FOR_PROJECTS, default_factory=list
392+
)
384393
async def load_participants_for_projects(
385-
project_ids: list[ProjectId], connection
386-
) -> list[list[ParticipantInternal]]:
394+
ids: list[ProjectId], filter_: ParticipantFilter, connection
395+
) -> dict[ProjectId, list[ParticipantInternal]]:
387396
"""
388397
Get all participants in a project
389398
"""
390399

391-
retval: list[list[ParticipantInternal]] = []
392-
393-
for project in project_ids:
394-
retval.append(
395-
await ParticipantLayer(connection).get_participants(project=project)
396-
)
400+
f = copy.copy(filter_)
401+
f.project = GenericFilter(in_=ids)
402+
participants = await ParticipantLayer(connection).query(f)
397403

398-
return retval
404+
pmap = group_by(participants, lambda p: p.project)
405+
return pmap
399406

400407

401408
@connected_data_loader_with_params(

api/graphql/schema.py

+42-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from api.graphql.loaders import LoaderKeys, get_context
2222
from db.python import enum_tables
23+
from db.python.filters import GenericFilter
2324
from db.python.layers import (
2425
AnalysisLayer,
2526
AnalysisRunnerLayer,
@@ -35,10 +36,10 @@
3536
from db.python.tables.assay import AssayFilter
3637
from db.python.tables.cohort import CohortFilter, CohortTemplateFilter
3738
from db.python.tables.family import FamilyFilter
39+
from db.python.tables.participant import ParticipantFilter
3840
from db.python.tables.project import ProjectPermissionsTable
3941
from db.python.tables.sample import SampleFilter
4042
from db.python.tables.sequencing_group import SequencingGroupFilter
41-
from db.python.utils import GenericFilter
4243
from models.enums import AnalysisStatus
4344
from models.models import (
4445
PRIMARY_EXTERNAL_ORG,
@@ -331,10 +332,39 @@ async def families(
331332

332333
@strawberry.field()
333334
async def participants(
334-
self, info: Info, root: 'Project'
335+
self,
336+
info: Info,
337+
root: 'GraphQLProject',
338+
id: GraphQLFilter[int] | None = None,
339+
external_id: GraphQLFilter[str] | None = None,
340+
meta: GraphQLMetaFilter | None = None,
341+
reported_sex: GraphQLFilter[int] | None = None,
342+
reported_gender: GraphQLFilter[str] | None = None,
343+
karyotype: GraphQLFilter[str] | None = None,
335344
) -> list['GraphQLParticipant']:
336345
loader = info.context[LoaderKeys.PARTICIPANTS_FOR_PROJECTS]
337-
participants = await loader.load(root.id)
346+
participants = await loader.load(
347+
{
348+
'id': root.id,
349+
'filter_': ParticipantFilter(
350+
project=GenericFilter(eq=root.id),
351+
id=id.to_internal_filter() if id else None,
352+
external_id=(
353+
external_id.to_internal_filter() if external_id else None
354+
),
355+
meta=graphql_meta_filter_to_internal_filter(meta),
356+
reported_gender=(
357+
reported_gender.to_internal_filter()
358+
if reported_gender
359+
else None
360+
),
361+
reported_sex=(
362+
reported_sex.to_internal_filter() if reported_sex else None
363+
),
364+
karyotype=karyotype.to_internal_filter() if karyotype else None,
365+
),
366+
}
367+
)
338368
return [GraphQLParticipant.from_internal(p) for p in participants]
339369

340370
@strawberry.field()
@@ -1181,8 +1211,10 @@ async def sequencing_groups(
11811211

11821212
filter_ = SequencingGroupFilter(
11831213
project=_project_filter,
1184-
sample_id=(
1185-
sample_id.to_internal_filter_mapped(sample_id_transform_to_raw)
1214+
sample=(
1215+
SequencingGroupFilter.SequencingGroupSampleFilter(
1216+
id=sample_id.to_internal_filter_mapped(sample_id_transform_to_raw)
1217+
)
11861218
if sample_id
11871219
else None
11881220
),
@@ -1200,7 +1232,11 @@ async def sequencing_groups(
12001232
else GenericFilter(eq=True)
12011233
),
12021234
created_on=created_on.to_internal_filter() if created_on else None,
1203-
assay_meta=assay_meta,
1235+
assay=(
1236+
SequencingGroupFilter.SequencingGroupAssayFilter(
1237+
meta=graphql_meta_filter_to_internal_filter(assay_meta),
1238+
)
1239+
),
12041240
has_cram=has_cram,
12051241
has_gvcf=has_gvcf,
12061242
)

api/routes/analysis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
get_projectless_db_connection,
1717
)
1818
from api.utils.export import ExportType
19+
from db.python.filters import GenericFilter
1920
from db.python.layers.analysis import AnalysisLayer
2021
from db.python.tables.analysis import AnalysisFilter
2122
from db.python.tables.project import ProjectPermissionsTable
22-
from db.python.utils import GenericFilter
2323
from models.enums import AnalysisStatus
2424
from models.models.analysis import (
2525
Analysis,

api/routes/analysis_runner.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
get_project_readonly_connection,
88
get_project_write_connection,
99
)
10+
from db.python.filters import GenericFilter
1011
from db.python.layers.analysis_runner import AnalysisRunnerLayer
1112
from db.python.tables.analysis_runner import AnalysisRunnerFilter
12-
from db.python.utils import GenericFilter
1313
from models.models.analysis_runner import AnalysisRunner, AnalysisRunnerInternal
1414

1515
router = APIRouter(prefix='/analysis-runner', tags=['analysis-runner'])

api/routes/assay.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from api.utils import get_project_readonly_connection
66
from api.utils.db import Connection, get_projectless_db_connection
7+
from db.python.filters import GenericFilter
78
from db.python.layers.assay import AssayLayer
89
from db.python.tables.assay import AssayFilter
910
from db.python.tables.project import ProjectPermissionsTable
10-
from db.python.utils import GenericFilter
1111
from models.base import SMBase
1212
from models.models.assay import AssayUpsert
1313
from models.utils.sample_id_format import sample_id_transform_to_raw_list

api/routes/family.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
)
1818
from api.utils.export import ExportType
1919
from api.utils.extensions import guess_delimiter_by_upload_file_obj
20+
from db.python.filters import GenericFilter
2021
from db.python.layers.family import FamilyLayer, PedRow
2122
from db.python.tables.family import FamilyFilter
22-
from db.python.utils import GenericFilter
2323
from models.models.family import Family
2424
from models.utils.sample_id_format import sample_id_transform_to_raw_list
2525

0 commit comments

Comments
 (0)