From 73875fba310d31773e12997d3a381cc67521d11e Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Mon, 17 Jun 2024 15:33:57 -0400 Subject: [PATCH 01/14] show RNA type in sample hover --- seqr/views/apis/family_api.py | 17 +++++++++++++++-- ui/shared/components/panel/sample.jsx | 17 ++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 2c4ce793a0..4b78882950 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -23,7 +23,7 @@ from seqr.views.utils.project_context_utils import add_families_context, families_discovery_tags, add_project_tag_types, \ MME_TAG_NAME from seqr.models import Family, FamilyAnalysedBy, Individual, FamilyNote, Sample, VariantTag, AnalysisGroup, RnaSeqTpm, \ - PhenotypePrioritization, Project + PhenotypePrioritization, Project, RnaSeqOutlier, RnaSeqSpliceOutlier from seqr.views.utils.permissions_utils import check_project_permissions, get_project_and_check_pm_permissions, \ login_and_policies_required, user_is_analyst, has_case_review_permissions from seqr.views.utils.variant_utils import get_phenotype_prioritization, get_omim_intervals_query, DISCOVERY_CATEGORY @@ -45,9 +45,22 @@ def family_page_data(request, family_guid): sample_models = Sample.objects.filter(individual__family=family) samples = get_json_for_samples(sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst) response = { - 'samplesByGuid': {s['sampleGuid']: s for s in samples}, + 'samplesByGuid': {s['sampleGuid']: {**s, 'rnaSeqTypes': []} for s in samples}, } + # Add Rna Seq metadata to samples + rna_seq_models = [ + (RnaSeqTpm, 'TPM'), + (RnaSeqOutlier, 'Outlier'), + (RnaSeqSpliceOutlier, 'Splice Outlier'), + ] + for model, rna_seq_type in rna_seq_models: + rna_seq_samples = model.objects.filter( + sample__in=sample_models, sample__is_active=True + ).values('sample__guid').distinct() + for rna_seq_sample in rna_seq_samples: + response['samplesByGuid'][rna_seq_sample['sample__guid']]['rnaSeqTypes'].append(rna_seq_type) + add_families_context(response, families, project.guid, request.user, is_analyst, has_case_review_perm) family_response = response['familiesByGuid'][family_guid] diff --git a/ui/shared/components/panel/sample.jsx b/ui/shared/components/panel/sample.jsx index 56bf048185..b09673e254 100644 --- a/ui/shared/components/panel/sample.jsx +++ b/ui/shared/components/panel/sample.jsx @@ -3,8 +3,8 @@ import PropTypes from 'prop-types' import styled from 'styled-components' import { Popup, Icon } from 'semantic-ui-react' -import { HorizontalSpacer } from '../Spacers' -import { DATASET_TYPE_SNV_INDEL_CALLS } from '../../utils/constants' +import { HorizontalSpacer, VerticalSpacer } from '../Spacers' +import { DATASET_TYPE_SNV_INDEL_CALLS, SAMPLE_TYPE_RNA } from '../../utils/constants' const Detail = styled.span` font-size: 11px; @@ -33,9 +33,16 @@ const Sample = React.memo(({ loadedSample, isOutdated, hoverDetails }) => ( } } - content={loadedSample ? - `data was${isOutdated ? ' previously ' : ''} ${hoverDetails ? `${hoverDetails} on ${new Date(loadedSample.loadedDate).toLocaleDateString()}` : 'loaded'}` : - 'no data available'} + content={ +
+ {loadedSample ? + `data was${isOutdated ? ' previously ' : ''} ${hoverDetails ? `${hoverDetails} on ${new Date(loadedSample.loadedDate).toLocaleDateString()}` : 'loaded'}` : + 'no data available'} + + {loadedSample?.sampleType && loadedSample.sampleType === SAMPLE_TYPE_RNA && + loadedSample?.rnaSeqTypes?.length > 0 && `RNAseq methods: ${loadedSample.rnaSeqTypes.join(', ')}`} +
+ } position="left center" /> )) From 9c6f2a688ac80ea96b8e35956d7026de106035cd Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Mon, 17 Jun 2024 16:59:26 -0400 Subject: [PATCH 02/14] remove hasRnaOutlierData --- seqr/views/apis/family_api.py | 5 ----- ui/pages/Project/components/FamilyTable/IndividualRow.jsx | 2 +- ui/shared/components/panel/sample.jsx | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 4b78882950..7182cb06b9 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -90,11 +90,6 @@ def family_page_data(request, family_guid): 'postDiscoveryOmimOptions': omim_map, }) - outlier_individual_guids = sample_models.filter(sample_type=Sample.SAMPLE_TYPE_RNA, is_active=True)\ - .exclude(rnaseqoutlier__isnull=True, rnaseqspliceoutlier__isnull=True).values_list('individual__guid', flat=True) - for individual_guid in outlier_individual_guids: - response['individualsByGuid'][individual_guid]['hasRnaOutlierData'] = True - tools_by_indiv = {} tools_agg = PhenotypePrioritization.objects.filter(individual__family=family).values('individual__guid').annotate( phenotypePrioritizationTools=ArrayAgg( diff --git a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx index 4cb355b7c1..8717ebdde3 100644 --- a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx +++ b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx @@ -169,7 +169,7 @@ const DataDetails = React.memo(({ loadedSamples, individual, mmeSubmission }) => /> ) : )} - {individual.hasRnaOutlierData && ( + {loadedSamples.some(sample => sample.rnaSeqTypes?.length > 0) && (
( `data was${isOutdated ? ' previously ' : ''} ${hoverDetails ? `${hoverDetails} on ${new Date(loadedSample.loadedDate).toLocaleDateString()}` : 'loaded'}` : 'no data available'} - {loadedSample?.sampleType && loadedSample.sampleType === SAMPLE_TYPE_RNA && - loadedSample?.rnaSeqTypes?.length > 0 && `RNAseq methods: ${loadedSample.rnaSeqTypes.join(', ')}`} + {loadedSample.sampleType && loadedSample.sampleType === SAMPLE_TYPE_RNA && + loadedSample.rnaSeqTypes?.length > 0 && `RNAseq methods: ${loadedSample.rnaSeqTypes.join(', ')}`}
} position="left center" From d6dfb8f1cb32eb1e545191747024457301f25e96 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Fri, 21 Jun 2024 12:58:02 -0400 Subject: [PATCH 03/14] additional values and post processing --- seqr/views/apis/family_api.py | 34 ++++++++++++++------------- seqr/views/utils/orm_to_json_utils.py | 5 ++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 7182cb06b9..158957f70e 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -5,7 +5,7 @@ from collections import defaultdict from django.contrib.auth.models import User from django.contrib.postgres.aggregates import ArrayAgg -from django.db.models import Count, Q +from django.db.models import Count, Q, Case, When, Value, Exists, OuterRef from django.db.models.fields.files import ImageFieldFile from django.db.models.functions import JSONObject, Concat, Upper, Substr @@ -43,23 +43,25 @@ def family_page_data(request, family_guid): has_case_review_perm = has_case_review_permissions(project, request.user) sample_models = Sample.objects.filter(individual__family=family) - samples = get_json_for_samples(sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst) - response = { - 'samplesByGuid': {s['sampleGuid']: {**s, 'rnaSeqTypes': []} for s in samples}, + additional_values = { + 'rnaSeqTpm': Case(When(Exists(RnaSeqTpm.objects.filter(sample_id=OuterRef('pk'))), then=Value('TPM')), default=None), + 'rnaSeqOutlier': Case(When(Exists(RnaSeqOutlier.objects.filter(sample_id=OuterRef('pk'))), then=Value('Outlier')), default=None), + 'rnaSeqSpliceOutlier': Case(When(Exists(RnaSeqSpliceOutlier.objects.filter(sample_id=OuterRef('pk'))), then=Value('Splice Outlier')), default=None), } + samples = get_json_for_samples( + sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst, + additional_values=additional_values + ) + samples_by_guid = {} + for sample in samples: + tpm, outlier, splice_outlier = sample.pop('rnaSeqTpm'), sample.pop('rnaSeqOutlier'), sample.pop('rnaSeqSpliceOutlier') + if sample['sampleType'] == 'RNA': + sample['rnaSeqTypes'] = [value for value in [tpm, outlier, splice_outlier] if value] + samples_by_guid[sample['sampleGuid']] = sample - # Add Rna Seq metadata to samples - rna_seq_models = [ - (RnaSeqTpm, 'TPM'), - (RnaSeqOutlier, 'Outlier'), - (RnaSeqSpliceOutlier, 'Splice Outlier'), - ] - for model, rna_seq_type in rna_seq_models: - rna_seq_samples = model.objects.filter( - sample__in=sample_models, sample__is_active=True - ).values('sample__guid').distinct() - for rna_seq_sample in rna_seq_samples: - response['samplesByGuid'][rna_seq_sample['sample__guid']]['rnaSeqTypes'].append(rna_seq_type) + response = { + 'samplesByGuid': {s['sampleGuid']: s for s in samples}, + } add_families_context(response, families, project.guid, request.user, is_analyst, has_case_review_perm) family_response = response['familiesByGuid'][family_guid] diff --git a/seqr/views/utils/orm_to_json_utils.py b/seqr/views/utils/orm_to_json_utils.py index 67a943fc8f..8939c99d4b 100644 --- a/seqr/views/utils/orm_to_json_utils.py +++ b/seqr/views/utils/orm_to_json_utils.py @@ -346,16 +346,17 @@ def _get_sample_json_kwargs(project_guid=None, family_guid=None, individual_guid return {'guid_key': 'sampleGuid', **additional_kwargs, **kwargs} -def get_json_for_samples(samples, **kwargs): +def get_json_for_samples(samples, additional_values=None, **kwargs): """Returns a JSON representation of the given list of Samples. Args: samples (array): array of django models for the Samples. + additional_values (dict): additional values to include in the json Returns: array: array of json objects """ - return get_json_for_queryset(samples, **_get_sample_json_kwargs(**kwargs)) + return get_json_for_queryset(samples, additional_values=additional_values, **_get_sample_json_kwargs(**kwargs)) def get_json_for_sample(sample, **kwargs): From 88c86c5622952a43f0f133aa165ade5e866ad6fd Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Fri, 21 Jun 2024 14:04:48 -0400 Subject: [PATCH 04/14] extra --- seqr/views/apis/family_api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 158957f70e..8711601d00 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -4,8 +4,9 @@ import json from collections import defaultdict from django.contrib.auth.models import User -from django.contrib.postgres.aggregates import ArrayAgg -from django.db.models import Count, Q, Case, When, Value, Exists, OuterRef +from django.contrib.postgres.aggregates import ArrayAgg, JSONBAgg, StringAgg +from django.contrib.postgres.fields import ArrayField +from django.db.models import Count, Q, Case, When, Value, CharField, F, Exists, OuterRef from django.db.models.fields.files import ImageFieldFile from django.db.models.functions import JSONObject, Concat, Upper, Substr @@ -52,12 +53,10 @@ def family_page_data(request, family_guid): sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst, additional_values=additional_values ) - samples_by_guid = {} for sample in samples: tpm, outlier, splice_outlier = sample.pop('rnaSeqTpm'), sample.pop('rnaSeqOutlier'), sample.pop('rnaSeqSpliceOutlier') if sample['sampleType'] == 'RNA': sample['rnaSeqTypes'] = [value for value in [tpm, outlier, splice_outlier] if value] - samples_by_guid[sample['sampleGuid']] = sample response = { 'samplesByGuid': {s['sampleGuid']: s for s in samples}, From c73e3f684999cff53c6d54298958f4ce4c1a6c38 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Fri, 21 Jun 2024 14:20:56 -0400 Subject: [PATCH 05/14] test --- seqr/views/apis/family_api_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/seqr/views/apis/family_api_tests.py b/seqr/views/apis/family_api_tests.py index eb22e8e85d..1664d72da6 100644 --- a/seqr/views/apis/family_api_tests.py +++ b/seqr/views/apis/family_api_tests.py @@ -31,6 +31,7 @@ INDIVIDUAL3_GUID = 'I000003_na19679' INDIVIDUAL_GUIDS = [INDIVIDUAL_GUID, INDIVIDUAL2_GUID, INDIVIDUAL3_GUID] +SAMPLE_GUIDS = ['S000129_na19675', 'S000130_na19678', 'S000131_na19679', 'S000151_na19675_1', 'S000152_na19675_d2', 'S000153_na19679'] class FamilyAPITest(AuthenticationTestCase): fixtures = ['users', '1kg_project', 'reference_data'] @@ -69,8 +70,7 @@ def test_family_page_data(self): self.assertEqual(len(response_json['individualsByGuid']), 3) individual = response_json['individualsByGuid'][INDIVIDUAL_GUID] - individual_fields = {'sampleGuids', 'igvSampleGuids', 'mmeSubmissionGuid', 'hasRnaOutlierData', - 'phenotypePrioritizationTools'} + individual_fields = {'sampleGuids', 'igvSampleGuids', 'mmeSubmissionGuid', 'phenotypePrioritizationTools'} individual_fields.update(INDIVIDUAL_FIELDS) self.assertSetEqual(set(individual.keys()), individual_fields) self.assertListEqual([ @@ -83,10 +83,6 @@ def test_family_page_data(self): ], [response_json['individualsByGuid'][guid].get('phenotypePrioritizationTools') for guid in INDIVIDUAL_GUIDS] ) - self.assertListEqual( - [True, False, True], - [response_json['individualsByGuid'][guid].get('hasRnaOutlierData', False) for guid in INDIVIDUAL_GUIDS] - ) self.assertSetEqual({PROJECT_GUID}, {i['projectGuid'] for i in response_json['individualsByGuid'].values()}) self.assertSetEqual({FAMILY_GUID}, {i['familyGuid'] for i in response_json['individualsByGuid'].values()}) @@ -96,6 +92,10 @@ def test_family_page_data(self): self.assertSetEqual({FAMILY_GUID}, {s['familyGuid'] for s in response_json['samplesByGuid'].values()}) self.assertEqual(len(individual['sampleGuids']), 3) self.assertTrue(set(individual['sampleGuids']).issubset(set(response_json['samplesByGuid'].keys()))) + self.assertListEqual( + [[], [], [], ['TPM', 'Splice Outlier'], ['TPM', 'Outlier', 'Splice Outlier'], ['Splice Outlier']], + [response_json['samplesByGuid'][guid].get('rnaSeqTypes', []) for guid in SAMPLE_GUIDS] + ) self.assertEqual(len(response_json['igvSamplesByGuid']), 1) self.assertSetEqual(set(next(iter(response_json['igvSamplesByGuid'].values())).keys()), IGV_SAMPLE_FIELDS) From a539726c4b8a49f3e8caea78819e5b5940c81462 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Fri, 21 Jun 2024 14:26:18 -0400 Subject: [PATCH 06/14] unused --- seqr/views/apis/family_api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 8711601d00..5fe95572ac 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -4,9 +4,8 @@ import json from collections import defaultdict from django.contrib.auth.models import User -from django.contrib.postgres.aggregates import ArrayAgg, JSONBAgg, StringAgg -from django.contrib.postgres.fields import ArrayField -from django.db.models import Count, Q, Case, When, Value, CharField, F, Exists, OuterRef +from django.contrib.postgres.aggregates import ArrayAgg +from django.db.models import Count, Q, Case, When, Value, Exists, OuterRef from django.db.models.fields.files import ImageFieldFile from django.db.models.functions import JSONObject, Concat, Upper, Substr From e2d52f1276a3fba40493e5bafb1ebd78a4bd1762 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Mon, 24 Jun 2024 13:57:22 -0400 Subject: [PATCH 07/14] with isnull --- seqr/views/apis/family_api.py | 13 +++++-------- seqr/views/apis/family_api_tests.py | 12 +++++++++--- seqr/views/utils/test_utils.py | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 5fe95572ac..50b294dc88 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -44,19 +44,16 @@ def family_page_data(request, family_guid): sample_models = Sample.objects.filter(individual__family=family) additional_values = { - 'rnaSeqTpm': Case(When(Exists(RnaSeqTpm.objects.filter(sample_id=OuterRef('pk'))), then=Value('TPM')), default=None), - 'rnaSeqOutlier': Case(When(Exists(RnaSeqOutlier.objects.filter(sample_id=OuterRef('pk'))), then=Value('Outlier')), default=None), - 'rnaSeqSpliceOutlier': Case(When(Exists(RnaSeqSpliceOutlier.objects.filter(sample_id=OuterRef('pk'))), then=Value('Splice Outlier')), default=None), + 'rnaSeqTypes': JSONObject( + hasRnaSeqTpm=Case(When(rnaseqtpm__isnull=False, then=True), default=False), + hasRnaSeqOutlier=Case(When(rnaseqoutlier__isnull=False, then=True), default=False), + hasRnaSeqSpliceOutlier=Case(When(rnaseqspliceoutlier__isnull=False, then=True), default=False), + ) } samples = get_json_for_samples( sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst, additional_values=additional_values ) - for sample in samples: - tpm, outlier, splice_outlier = sample.pop('rnaSeqTpm'), sample.pop('rnaSeqOutlier'), sample.pop('rnaSeqSpliceOutlier') - if sample['sampleType'] == 'RNA': - sample['rnaSeqTypes'] = [value for value in [tpm, outlier, splice_outlier] if value] - response = { 'samplesByGuid': {s['sampleGuid']: s for s in samples}, } diff --git a/seqr/views/apis/family_api_tests.py b/seqr/views/apis/family_api_tests.py index 1664d72da6..d585018b1c 100644 --- a/seqr/views/apis/family_api_tests.py +++ b/seqr/views/apis/family_api_tests.py @@ -92,9 +92,15 @@ def test_family_page_data(self): self.assertSetEqual({FAMILY_GUID}, {s['familyGuid'] for s in response_json['samplesByGuid'].values()}) self.assertEqual(len(individual['sampleGuids']), 3) self.assertTrue(set(individual['sampleGuids']).issubset(set(response_json['samplesByGuid'].keys()))) - self.assertListEqual( - [[], [], [], ['TPM', 'Splice Outlier'], ['TPM', 'Outlier', 'Splice Outlier'], ['Splice Outlier']], - [response_json['samplesByGuid'][guid].get('rnaSeqTypes', []) for guid in SAMPLE_GUIDS] + expected_rna_seq_types = [ + {'hasRnaSeqTpm': False, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': False} for _ in range(3) + ] + [ + {'hasRnaSeqTpm': True, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': True}, + {'hasRnaSeqTpm': True, 'hasRnaSeqOutlier': True, 'hasRnaSeqSpliceOutlier': True}, + {'hasRnaSeqTpm': False, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': True}, + ] + self.assertListEqual(expected_rna_seq_types, + [response_json['samplesByGuid'][guid]['rnaSeqTypes'] for guid in SAMPLE_GUIDS] ) self.assertEqual(len(response_json['igvSamplesByGuid']), 1) diff --git a/seqr/views/utils/test_utils.py b/seqr/views/utils/test_utils.py index f0ed234d4f..bb0d509835 100644 --- a/seqr/views/utils/test_utils.py +++ b/seqr/views/utils/test_utils.py @@ -793,7 +793,7 @@ def _get_list_param(call, param): SAMPLE_FIELDS = { 'projectGuid', 'familyGuid', 'individualGuid', 'sampleGuid', 'createdDate', 'sampleType', 'sampleId', 'isActive', - 'loadedDate', 'datasetType', 'elasticsearchIndex', + 'loadedDate', 'datasetType', 'elasticsearchIndex', 'rnaSeqTypes' } IGV_SAMPLE_FIELDS = { From 6268b1041b24107a25359bbd169f5ad8bc6b49a1 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Mon, 24 Jun 2024 13:58:04 -0400 Subject: [PATCH 08/14] with outerref --- seqr/views/apis/family_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 50b294dc88..1987a849a8 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -45,9 +45,9 @@ def family_page_data(request, family_guid): sample_models = Sample.objects.filter(individual__family=family) additional_values = { 'rnaSeqTypes': JSONObject( - hasRnaSeqTpm=Case(When(rnaseqtpm__isnull=False, then=True), default=False), - hasRnaSeqOutlier=Case(When(rnaseqoutlier__isnull=False, then=True), default=False), - hasRnaSeqSpliceOutlier=Case(When(rnaseqspliceoutlier__isnull=False, then=True), default=False), + hasRnaSeqTpm=Case(When(Exists(RnaSeqTpm.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), + hasRnaSeqOutlier=Case(When(Exists(RnaSeqOutlier.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), + hasRnaSeqSpliceOutlier=Case(When(Exists(RnaSeqSpliceOutlier.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), ) } samples = get_json_for_samples( From 50213d9d89841c77fbf8d0c6ff82270d2e348426 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Tue, 25 Jun 2024 13:33:15 -0400 Subject: [PATCH 09/14] changes --- seqr/views/apis/family_api.py | 21 +++++++++++---------- seqr/views/apis/family_api_tests.py | 12 +++--------- seqr/views/utils/orm_to_json_utils.py | 4 ++-- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 1987a849a8..4e862174fb 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -43,20 +43,21 @@ def family_page_data(request, family_guid): has_case_review_perm = has_case_review_permissions(project, request.user) sample_models = Sample.objects.filter(individual__family=family) - additional_values = { - 'rnaSeqTypes': JSONObject( - hasRnaSeqTpm=Case(When(Exists(RnaSeqTpm.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), - hasRnaSeqOutlier=Case(When(Exists(RnaSeqOutlier.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), - hasRnaSeqSpliceOutlier=Case(When(Exists(RnaSeqSpliceOutlier.objects.filter(sample_id=OuterRef('pk'))), then=True), default=False), - ) - } samples = get_json_for_samples( - sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst, - additional_values=additional_values + sample_models, project_guid=project.guid, family_guid=family_guid, skip_nested=True, is_analyst=is_analyst ) response = { - 'samplesByGuid': {s['sampleGuid']: s for s in samples}, + 'samplesByGuid': {s['sampleGuid']: s for s in samples} + } + rna_type_samples = { + 'TPM': set(RnaSeqTpm.objects.filter(sample__in=sample_models).values_list('sample__guid', flat=True)), + 'Expression Outlier': set(RnaSeqOutlier.objects.filter(sample__in=sample_models).values_list('sample__guid', flat=True)), + 'Splice Outlier': set(RnaSeqSpliceOutlier.objects.filter(sample__in=sample_models).values_list('sample__guid', flat=True)), } + for sample in response['samplesByGuid'].values(): + sample['rnaSeqTypes'] = [ + rnaseq_type for rnaseq_type, sample_ids in rna_type_samples.items() if sample['sampleGuid'] in sample_ids + ] add_families_context(response, families, project.guid, request.user, is_analyst, has_case_review_perm) family_response = response['familiesByGuid'][family_guid] diff --git a/seqr/views/apis/family_api_tests.py b/seqr/views/apis/family_api_tests.py index d585018b1c..cbbcb8f842 100644 --- a/seqr/views/apis/family_api_tests.py +++ b/seqr/views/apis/family_api_tests.py @@ -92,15 +92,9 @@ def test_family_page_data(self): self.assertSetEqual({FAMILY_GUID}, {s['familyGuid'] for s in response_json['samplesByGuid'].values()}) self.assertEqual(len(individual['sampleGuids']), 3) self.assertTrue(set(individual['sampleGuids']).issubset(set(response_json['samplesByGuid'].keys()))) - expected_rna_seq_types = [ - {'hasRnaSeqTpm': False, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': False} for _ in range(3) - ] + [ - {'hasRnaSeqTpm': True, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': True}, - {'hasRnaSeqTpm': True, 'hasRnaSeqOutlier': True, 'hasRnaSeqSpliceOutlier': True}, - {'hasRnaSeqTpm': False, 'hasRnaSeqOutlier': False, 'hasRnaSeqSpliceOutlier': True}, - ] - self.assertListEqual(expected_rna_seq_types, - [response_json['samplesByGuid'][guid]['rnaSeqTypes'] for guid in SAMPLE_GUIDS] + self.assertListEqual( + [[], [], [], ['TPM', 'Splice Outlier'], ['TPM', 'Expression Outlier', 'Splice Outlier'], ['Splice Outlier']], + [response_json['samplesByGuid'][guid].get('rnaSeqTypes', {}) for guid in SAMPLE_GUIDS] ) self.assertEqual(len(response_json['igvSamplesByGuid']), 1) diff --git a/seqr/views/utils/orm_to_json_utils.py b/seqr/views/utils/orm_to_json_utils.py index 8939c99d4b..e14ad01066 100644 --- a/seqr/views/utils/orm_to_json_utils.py +++ b/seqr/views/utils/orm_to_json_utils.py @@ -346,7 +346,7 @@ def _get_sample_json_kwargs(project_guid=None, family_guid=None, individual_guid return {'guid_key': 'sampleGuid', **additional_kwargs, **kwargs} -def get_json_for_samples(samples, additional_values=None, **kwargs): +def get_json_for_samples(samples, **kwargs): """Returns a JSON representation of the given list of Samples. Args: @@ -356,7 +356,7 @@ def get_json_for_samples(samples, additional_values=None, **kwargs): array: array of json objects """ - return get_json_for_queryset(samples, additional_values=additional_values, **_get_sample_json_kwargs(**kwargs)) + return get_json_for_queryset(samples, **_get_sample_json_kwargs(**kwargs)) def get_json_for_sample(sample, **kwargs): From 6602382908760227c1ebe636b228a7b5425119e2 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Tue, 25 Jun 2024 13:34:59 -0400 Subject: [PATCH 10/14] lint stuff --- seqr/views/apis/family_api.py | 2 +- seqr/views/utils/orm_to_json_utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/seqr/views/apis/family_api.py b/seqr/views/apis/family_api.py index 4e862174fb..dcae71adeb 100644 --- a/seqr/views/apis/family_api.py +++ b/seqr/views/apis/family_api.py @@ -5,7 +5,7 @@ from collections import defaultdict from django.contrib.auth.models import User from django.contrib.postgres.aggregates import ArrayAgg -from django.db.models import Count, Q, Case, When, Value, Exists, OuterRef +from django.db.models import Count, Q from django.db.models.fields.files import ImageFieldFile from django.db.models.functions import JSONObject, Concat, Upper, Substr diff --git a/seqr/views/utils/orm_to_json_utils.py b/seqr/views/utils/orm_to_json_utils.py index e14ad01066..67a943fc8f 100644 --- a/seqr/views/utils/orm_to_json_utils.py +++ b/seqr/views/utils/orm_to_json_utils.py @@ -351,7 +351,6 @@ def get_json_for_samples(samples, **kwargs): Args: samples (array): array of django models for the Samples. - additional_values (dict): additional values to include in the json Returns: array: array of json objects """ From a28272a935f32368f887a0eab848a37aabdd3a93 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Tue, 25 Jun 2024 13:41:18 -0400 Subject: [PATCH 11/14] key from project api tests? --- seqr/views/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seqr/views/utils/test_utils.py b/seqr/views/utils/test_utils.py index bb0d509835..f0ed234d4f 100644 --- a/seqr/views/utils/test_utils.py +++ b/seqr/views/utils/test_utils.py @@ -793,7 +793,7 @@ def _get_list_param(call, param): SAMPLE_FIELDS = { 'projectGuid', 'familyGuid', 'individualGuid', 'sampleGuid', 'createdDate', 'sampleType', 'sampleId', 'isActive', - 'loadedDate', 'datasetType', 'elasticsearchIndex', 'rnaSeqTypes' + 'loadedDate', 'datasetType', 'elasticsearchIndex', } IGV_SAMPLE_FIELDS = { From 41ffb9b2e901b7f454a9c768e7a9933345bb99c3 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Tue, 25 Jun 2024 13:52:35 -0400 Subject: [PATCH 12/14] fix the last test --- seqr/views/apis/family_api_tests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/seqr/views/apis/family_api_tests.py b/seqr/views/apis/family_api_tests.py index cbbcb8f842..bd43d8d697 100644 --- a/seqr/views/apis/family_api_tests.py +++ b/seqr/views/apis/family_api_tests.py @@ -31,8 +31,11 @@ INDIVIDUAL3_GUID = 'I000003_na19679' INDIVIDUAL_GUIDS = [INDIVIDUAL_GUID, INDIVIDUAL2_GUID, INDIVIDUAL3_GUID] + +FAMILY_API_SAMPLE_FIELDS = {*SAMPLE_FIELDS, 'rnaSeqTypes'} SAMPLE_GUIDS = ['S000129_na19675', 'S000130_na19678', 'S000131_na19679', 'S000151_na19675_1', 'S000152_na19675_d2', 'S000153_na19679'] + class FamilyAPITest(AuthenticationTestCase): fixtures = ['users', '1kg_project', 'reference_data'] @@ -87,7 +90,7 @@ def test_family_page_data(self): self.assertSetEqual({FAMILY_GUID}, {i['familyGuid'] for i in response_json['individualsByGuid'].values()}) self.assertEqual(len(response_json['samplesByGuid']), 6) - self.assertSetEqual(set(next(iter(response_json['samplesByGuid'].values())).keys()), SAMPLE_FIELDS) + self.assertSetEqual(set(next(iter(response_json['samplesByGuid'].values())).keys()), FAMILY_API_SAMPLE_FIELDS) self.assertSetEqual({PROJECT_GUID}, {s['projectGuid'] for s in response_json['samplesByGuid'].values()}) self.assertSetEqual({FAMILY_GUID}, {s['familyGuid'] for s in response_json['samplesByGuid'].values()}) self.assertEqual(len(individual['sampleGuids']), 3) From c1fdc7844e783eb971acee50d071fc4c625f9ee7 Mon Sep 17 00:00:00 2001 From: Julia Klugherz Date: Thu, 27 Jun 2024 16:22:01 -0400 Subject: [PATCH 13/14] fix individualrow --- ui/pages/Project/components/FamilyTable/IndividualRow.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx index 8717ebdde3..0d9c035f82 100644 --- a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx +++ b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx @@ -169,7 +169,7 @@ const DataDetails = React.memo(({ loadedSamples, individual, mmeSubmission }) => /> ) : )} - {loadedSamples.some(sample => sample.rnaSeqTypes?.length > 0) && ( + {loadedSamples.some(sample => sample.isActive && (sample.rnaSeqTypes.includes('Expression Outlier') || sample.rnaSeqTypes.includes('Splice Outlier'))) && (
Date: Thu, 27 Jun 2024 16:43:43 -0400 Subject: [PATCH 14/14] isactive --- ui/shared/components/panel/sample.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/shared/components/panel/sample.jsx b/ui/shared/components/panel/sample.jsx index f19d078a0c..c682c892b4 100644 --- a/ui/shared/components/panel/sample.jsx +++ b/ui/shared/components/panel/sample.jsx @@ -40,7 +40,7 @@ const Sample = React.memo(({ loadedSample, isOutdated, hoverDetails }) => ( 'no data available'} {loadedSample.sampleType && loadedSample.sampleType === SAMPLE_TYPE_RNA && - loadedSample.rnaSeqTypes?.length > 0 && `RNAseq methods: ${loadedSample.rnaSeqTypes.join(', ')}`} + loadedSample.isActive && `RNAseq methods: ${loadedSample.rnaSeqTypes.join(', ')}`}
} position="left center"