Skip to content

Commit

Permalink
Merge pull request #4488 from broadinstitute/report-variant-note
Browse files Browse the repository at this point in the history
Report variant note
  • Loading branch information
hanars authored Nov 21, 2024
2 parents 51e0ec0 + 5f05646 commit 40151b8
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# _seqr_ Changes

## dev
* Migrate "Submit to Clinvar" to generic report flag for Variant Notes (REQUIRES DB MIGRATION)

## 10/28/24
* Update RNA Tissue Type choices (REQUIRES DB MIGRATION)
Expand Down
4 changes: 2 additions & 2 deletions seqr/fixtures/1kg_project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2177,7 +2177,7 @@
"last_modified_date": "2018-02-23T17:32:23.054Z",
"saved_variants": [2],
"note": "test n\u00f8te",
"submit_to_clinvar": false,
"report": false,
"search_hash": null
}
},
Expand All @@ -2191,7 +2191,7 @@
"last_modified_date": "2019-02-23T17:32:23.054Z",
"saved_variants": [2],
"note": "a later note",
"submit_to_clinvar": false,
"report": false,
"search_hash": null
}
},
Expand Down
13 changes: 13 additions & 0 deletions seqr/fixtures/report_variants.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@
"search_hash": null
}
},
{
"model": "seqr.variantnote",
"pk": 123,
"fields": {
"guid": "VN0000123_prefix_19107_DEL_r0",
"created_date": "2019-05-15T14:51:58.410Z",
"created_by": null,
"last_modified_date": "2019-02-23T17:32:23.054Z",
"saved_variants": [7],
"note": "Phasing incorrect in input VCF",
"report": true
}
},
{
"model": "seqr.variantfunctionaldata",
"pk": 29,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-11-19 15:19

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('seqr', '0077_alter_rnasample_tissue_type'),
]

operations = [
migrations.RenameField(
model_name='variantnote',
old_name='submit_to_clinvar',
new_name='report',
),
]
4 changes: 2 additions & 2 deletions seqr/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ class Meta:
class VariantNote(ModelWithGUID):
saved_variants = models.ManyToManyField('SavedVariant')
note = models.TextField()
submit_to_clinvar = models.BooleanField(default=False)
report = models.BooleanField(default=False)

# these are for context
search_hash = models.CharField(max_length=50, null=True)
Expand All @@ -915,7 +915,7 @@ def __unicode__(self):
GUID_PREFIX = 'VN'

class Meta:
json_fields = ['guid', 'note', 'submit_to_clinvar', 'last_modified_date', 'created_by']
json_fields = ['guid', 'note', 'report', 'last_modified_date', 'created_by']


class VariantFunctionalData(ModelWithGUID):
Expand Down
5 changes: 3 additions & 2 deletions seqr/views/apis/report_api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,8 @@
], [
'Broad_NA20889_1_249045487_DEL', 'Broad_NA20889', '', 'SV', 'GRCh37', '1', '249045487', '', '', '',
'OR4G11P', '', '', '', 'Heterozygous', '', 'unknown', 'Broad_NA20889_1_248367227', '', 'Candidate',
'Immunodeficiency 38', 'OMIM:616126', 'Autosomal recessive', 'Full', '', '', 'SR-ES', '', 'DEL', '',
'249045898', '1', 'DEL:chr1:249045123-249045456', '',
'Immunodeficiency 38', 'OMIM:616126', 'Autosomal recessive', 'Full', '', '', 'SR-ES',
'Phasing incorrect in input VCF', 'DEL', '', '249045898', '1', 'DEL:chr1:249045123-249045456', '',
],
]

Expand Down Expand Up @@ -1428,6 +1428,7 @@ def test_variant_metadata(self):
'gene_id': None,
'gene_known_for_phenotype': 'Candidate',
'genetic_findings_id': 'NA20889_1_249045487_DEL',
'notes': 'Phasing incorrect in input VCF',
'participant_id': 'NA20889',
'pos': 249045487,
'projectGuid': 'R0003_test',
Expand Down
2 changes: 1 addition & 1 deletion seqr/views/apis/saved_variant_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def create_variant_note_handler(request, variant_guids):
def _create_variant_note(saved_variants, note_json, user):
note = create_model_from_json(VariantNote, {
'note': note_json.get('note'),
'submit_to_clinvar': note_json.get('submitToClinvar') or False,
'report': note_json.get('report') or False,
'search_hash': note_json.get('searchHash'),
}, user)
note.saved_variants.set(saved_variants)
Expand Down
28 changes: 14 additions & 14 deletions seqr/views/apis/saved_variant_api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,19 +518,19 @@ def test_create_update_and_delete_variant_note(self):

# send valid request to create variant_note
response = self.client.post(create_variant_note_url, content_type='application/json', data=json.dumps(
{'note': 'new_variant_note', 'submitToClinvar': True, 'familyGuid': 'F000001_1'}
{'note': 'new_variant_note', 'report': True, 'familyGuid': 'F000001_1'}
))

self.assertEqual(response.status_code, 200)
new_note_guid = response.json()['savedVariantsByGuid'][VARIANT_GUID]['noteGuids'][0]
new_note_response = response.json()['variantNotesByGuid'][new_note_guid]
self.assertEqual(new_note_response['note'], 'new_variant_note')
self.assertEqual(new_note_response['submitToClinvar'], True)
self.assertEqual(new_note_response['report'], True)

new_variant_note = VariantNote.objects.filter(guid=new_note_guid).first()
self.assertIsNotNone(new_variant_note)
self.assertEqual(new_variant_note.note, new_note_response['note'])
self.assertEqual(new_variant_note.submit_to_clinvar, new_note_response['submitToClinvar'])
self.assertEqual(new_variant_note.report, new_note_response['report'])

# save variant_note as gene_note
response = self.client.post(create_variant_note_url, content_type='application/json', data=json.dumps(
Expand Down Expand Up @@ -568,18 +568,18 @@ def test_create_update_and_delete_variant_note(self):
# update the variant_note
update_variant_note_url = reverse(update_variant_note_handler, args=[VARIANT_GUID, new_note_guid])
response = self.client.post(update_variant_note_url, content_type='application/json', data=json.dumps(
{'note': 'updated_variant_note', 'submitToClinvar': False}))
{'note': 'updated_variant_note', 'report': False}))

self.assertEqual(response.status_code, 200)

updated_note_response = response.json()['variantNotesByGuid'][new_note_guid]
self.assertEqual(updated_note_response['note'], 'updated_variant_note')
self.assertEqual(updated_note_response['submitToClinvar'], False)
self.assertEqual(updated_note_response['report'], False)

updated_variant_note = VariantNote.objects.filter(guid=updated_note_response['noteGuid']).first()
self.assertIsNotNone(updated_variant_note)
self.assertEqual(updated_variant_note.note, updated_note_response['note'])
self.assertEqual(updated_variant_note.submit_to_clinvar, updated_note_response['submitToClinvar'])
self.assertEqual(updated_variant_note.report, updated_note_response['report'])

# delete the variant_note
delete_variant_note_url = reverse(delete_variant_note_handler, args=[VARIANT_GUID, updated_variant_note.guid])
Expand All @@ -603,7 +603,7 @@ def test_create_partially_saved_compound_het_variant_note(self):
'tagGuids': ['VT1708633_2103343353_r0390_100', 'VT1726961_2103343353_r0390_100'], 'noteGuids': []},
],
'note': 'one_saved_one_not_saved_compount_hets_note',
'submitToClinvar': True,
'report': True,
'familyGuid': 'F000001_1',
}
response = self.client.post(create_saved_variant_url, content_type='application/json', data=json.dumps(request_body))
Expand Down Expand Up @@ -643,20 +643,20 @@ def test_create_update_and_delete_compound_hets_variant_note(self):
invalid_comp_hets_variant_note_url = reverse(
create_variant_note_handler, args=['not_variant,{}'.format(COMPOUND_HET_1_GUID)])
response = self.client.post(invalid_comp_hets_variant_note_url, content_type='application/json', data=json.dumps(
{'note': 'new_compound_hets_variant_note', 'submitToClinvar': True, 'familyGuid': 'F000001_1'}
{'note': 'new_compound_hets_variant_note', 'report': True, 'familyGuid': 'F000001_1'}
))
self.assertEqual(response.status_code, 400)
self.assertDictEqual(response.json(), {'error': 'Unable to find the following variant(s): not_variant'})

response = self.client.post(create_comp_hets_variant_note_url, content_type='application/json', data=json.dumps(
{'note': 'new_compound_hets_variant_note', 'submitToClinvar': True, 'familyGuid': 'F000001_1'}
{'note': 'new_compound_hets_variant_note', 'report': True, 'familyGuid': 'F000001_1'}
))

self.assertEqual(response.status_code, 200)
response_json = response.json()
for note in response.json()['variantNotesByGuid'].values():
self.assertEqual(note['note'], 'new_compound_hets_variant_note')
self.assertEqual(note['submitToClinvar'], True)
self.assertEqual(note['report'], True)

self.assertEqual(
response_json['savedVariantsByGuid'][COMPOUND_HET_1_GUID]['noteGuids'][0],
Expand All @@ -666,24 +666,24 @@ def test_create_update_and_delete_compound_hets_variant_note(self):
new_variant_note = VariantNote.objects.get(guid=new_note_guid)
self.assertEqual(new_variant_note.note, response_json['variantNotesByGuid'][new_note_guid]['note'])
self.assertEqual(
new_variant_note.submit_to_clinvar, response_json['variantNotesByGuid'][new_note_guid]['submitToClinvar']
new_variant_note.report, response_json['variantNotesByGuid'][new_note_guid]['report']
)

# update the variants_note for both compound hets
update_variant_note_url = reverse(update_variant_note_handler,
args=[','.join([COMPOUND_HET_1_GUID, COMPOUND_HET_2_GUID]), new_note_guid])
response = self.client.post(update_variant_note_url, content_type='application/json', data=json.dumps(
{'note': 'updated_variant_note', 'submitToClinvar': False}))
{'note': 'updated_variant_note', 'report': False}))

self.assertEqual(response.status_code, 200)

updated_note_response = response.json()['variantNotesByGuid'][new_note_guid]
self.assertEqual(updated_note_response['note'], 'updated_variant_note')
self.assertEqual(updated_note_response['submitToClinvar'], False)
self.assertEqual(updated_note_response['report'], False)

updated_variant_note = VariantNote.objects.get(guid=new_note_guid)
self.assertEqual(updated_variant_note.note, updated_note_response['note'])
self.assertEqual(updated_variant_note.submit_to_clinvar, updated_note_response['submitToClinvar'])
self.assertEqual(updated_variant_note.report, updated_note_response['report'])

# save variant_note as gene_note for both compound hets
response = self.client.post(
Expand Down
2 changes: 1 addition & 1 deletion seqr/views/apis/summary_data_api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
'chrom_end-1': None,
'pos_end-1': None,
'notes-1': '',
'notes-2': '',
'notes-2': 'Phasing incorrect in input VCF',
'phenotype_contribution-1': 'Partial',
'phenotype_contribution-2': 'Full',
'partial_contribution_explained-1': 'HP:0000501|HP:0000365',
Expand Down
4 changes: 3 additions & 1 deletion seqr/views/utils/anvil_metadata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def _get_parsed_saved_discovery_variants_by_family(

annotations = dict(
tags=ArrayAgg('varianttag__variant_tag_type__name', distinct=True),
notes=ArrayAgg('variantnote__note', distinct=True, filter=Q(variantnote__report=True)),
partial_hpo_terms=ArrayAgg('variantfunctionaldata__metadata', distinct=True, filter=Q(variantfunctionaldata__functional_data_tag='Partial Phenotype Contribution')),
validated_name=ArrayAgg('variantfunctionaldata__metadata', distinct=True, filter=Q(variantfunctionaldata__functional_data_tag='Validated Name')),
)
Expand Down Expand Up @@ -364,6 +365,7 @@ def _get_parsed_saved_discovery_variants_by_family(
'gene_known_for_phenotype': 'Known' if 'Known gene for phenotype' in variant.tags else 'Candidate',
'phenotype_contribution': phenotype_contribution,
'partial_contribution_explained': partial_hpo_terms.replace(', ', '|'),
'notes': variant.notes,
'sv_type': sv_type,
'sv_name': (variant_json.get('svName') or '{svType}:chr{chrom}:{pos}-{end}'.format(**variant_json)) if sv_type else None,
'variant_type': variant_type,
Expand Down Expand Up @@ -527,7 +529,7 @@ def _get_genetic_findings_rows(rows: list[dict], individual: Individual, family_
del row['genotypes']

gene_variants = variants_by_gene[row[GENE_COLUMN]]
notes = []
notes = row['notes'] or []
if len(gene_variants) > 2:
discovery_notes = _get_discovery_notes(row, gene_variants, omit_parent_mnvs)
if discovery_notes is None:
Expand Down
2 changes: 1 addition & 1 deletion seqr/views/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ def _get_list_param(call, param):
'tagGuid', 'name', 'category', 'color', 'searchHash', 'metadata', 'lastModifiedDate', 'createdBy', 'variantGuids',
}

VARIANT_NOTE_FIELDS = {'noteGuid', 'note', 'submitToClinvar', 'lastModifiedDate', 'createdBy', 'variantGuids'}
VARIANT_NOTE_FIELDS = {'noteGuid', 'note', 'report', 'lastModifiedDate', 'createdBy', 'variantGuids'}

FUNCTIONAL_FIELDS = {
'tagGuid', 'name', 'color', 'metadata', 'metadataTitle', 'lastModifiedDate', 'createdBy', 'variantGuids',
Expand Down
30 changes: 12 additions & 18 deletions ui/shared/components/panel/variants/FamilyVariantTags.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getVariantId,
getMmeSubmissionsByGuid,
getGenesById,
getUser,
} from 'redux/selectors'
import { DISCOVERY_CATEGORY_NAME, MME_TAG_NAME, GREGOR_FINDING_TAG_NAME } from 'shared/utils/constants'
import { snakecaseToTitlecase } from 'shared/utils/stringUtils'
Expand All @@ -32,32 +33,23 @@ const TagTitle = styled.span`
color: #999;
`

const RedItal = styled.i`
color: red;
`

const NO_DISPLAY = { display: 'none' }

const SHORTCUT_TAGS = ['Review', 'Excluded']

const VARIANT_NOTE_FIELDS = [{
name: 'submitToClinvar',
label: (
<label>
Add to
<RedItal>&nbsp; ClinVar &nbsp;</RedItal>
submission
</label>
),
component: BooleanCheckbox,
style: { paddingTop: '2em' },
},
{
name: 'saveAsGeneNote',
label: 'Add to public gene notes',
component: BooleanCheckbox,
}]

const ANALYST_VARIANT_NOTE_FIELDS = [{
name: 'report',
label: 'Include in report notes',
component: BooleanCheckbox,
}, ...VARIANT_NOTE_FIELDS,
]

const DEPRECATED_MME_TAG = 'seqr MME (old)'
const AIP_TAG_TYPE = 'AIP'
const NO_EDIT_TAG_TYPES = [AIP_TAG_TYPE, GREGOR_FINDING_TAG_NAME]
Expand Down Expand Up @@ -258,7 +250,7 @@ MatchmakerLabel.propTypes = {
const FamilyVariantTags = React.memo(({
variant, variantTagNotes, family, projectTagTypes, projectFunctionalTagTypes, dispatchUpdateVariantNote,
dispatchUpdateFamilyVariantTags, dispatchUpdateFamilyVariantFunctionalTags, isCompoundHet, variantId,
linkToSavedVariants, mmeSubmissionsByGuid, genesById,
linkToSavedVariants, mmeSubmissionsByGuid, genesById, user,
}) => (
family ? (
<NoBorderTable basic="very" compact="very" celled>
Expand Down Expand Up @@ -337,7 +329,7 @@ const FamilyVariantTags = React.memo(({
initialValues={variantTagNotes}
modalId={family.familyGuid}
modalTitle={`Variant Note for Family ${family.displayName}`}
additionalEditFields={VARIANT_NOTE_FIELDS}
additionalEditFields={user.isAnalyst ? ANALYST_VARIANT_NOTE_FIELDS : VARIANT_NOTE_FIELDS}
defaultId={variantId}
idField="variantGuids"
isEditable
Expand Down Expand Up @@ -367,6 +359,7 @@ FamilyVariantTags.propTypes = {
dispatchUpdateFamilyVariantFunctionalTags: PropTypes.func.isRequired,
mmeSubmissionsByGuid: PropTypes.object,
genesById: PropTypes.object,
user: PropTypes.object,
}

FamilyVariantTags.defaultProps = {
Expand All @@ -386,6 +379,7 @@ const mapStateToProps = (state, ownProps) => {
variantTagNotes: ((getVariantTagNotesByFamilyVariants(state) || {})[ownProps.familyGuid] || {})[variantId],
mmeSubmissionsByGuid: getMmeSubmissionsByGuid(state),
genesById: getGenesById(state),
user: getUser(state),
}
}

Expand Down

0 comments on commit 40151b8

Please sign in to comment.