diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8e4e7c1d4..d264ec69bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/seqr/fixtures/1kg_project.json b/seqr/fixtures/1kg_project.json
index 3b737b363b..03f05701b7 100644
--- a/seqr/fixtures/1kg_project.json
+++ b/seqr/fixtures/1kg_project.json
@@ -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
}
},
@@ -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
}
},
diff --git a/seqr/fixtures/report_variants.json b/seqr/fixtures/report_variants.json
index 06ed7b2d11..fb5e2e4ad7 100644
--- a/seqr/fixtures/report_variants.json
+++ b/seqr/fixtures/report_variants.json
@@ -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,
diff --git a/seqr/migrations/0078_rename_submit_to_clinvar_variantnote_report.py b/seqr/migrations/0078_rename_submit_to_clinvar_variantnote_report.py
new file mode 100644
index 0000000000..d9071e0269
--- /dev/null
+++ b/seqr/migrations/0078_rename_submit_to_clinvar_variantnote_report.py
@@ -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',
+ ),
+ ]
diff --git a/seqr/models.py b/seqr/models.py
index 5461deec8c..1dd9135acc 100644
--- a/seqr/models.py
+++ b/seqr/models.py
@@ -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)
@@ -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):
diff --git a/seqr/views/apis/report_api_tests.py b/seqr/views/apis/report_api_tests.py
index 0ccb56bd20..db3dbb36f0 100644
--- a/seqr/views/apis/report_api_tests.py
+++ b/seqr/views/apis/report_api_tests.py
@@ -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', '',
],
]
@@ -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',
diff --git a/seqr/views/apis/saved_variant_api.py b/seqr/views/apis/saved_variant_api.py
index 18740ba3b9..af850a667c 100644
--- a/seqr/views/apis/saved_variant_api.py
+++ b/seqr/views/apis/saved_variant_api.py
@@ -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)
diff --git a/seqr/views/apis/saved_variant_api_tests.py b/seqr/views/apis/saved_variant_api_tests.py
index 1d33fc37c9..53baaa50e6 100644
--- a/seqr/views/apis/saved_variant_api_tests.py
+++ b/seqr/views/apis/saved_variant_api_tests.py
@@ -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(
@@ -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])
@@ -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))
@@ -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],
@@ -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(
diff --git a/seqr/views/apis/summary_data_api_tests.py b/seqr/views/apis/summary_data_api_tests.py
index 15df14d46c..199b4bd08b 100644
--- a/seqr/views/apis/summary_data_api_tests.py
+++ b/seqr/views/apis/summary_data_api_tests.py
@@ -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',
diff --git a/seqr/views/utils/anvil_metadata_utils.py b/seqr/views/utils/anvil_metadata_utils.py
index 8874a017eb..2a9e287c00 100644
--- a/seqr/views/utils/anvil_metadata_utils.py
+++ b/seqr/views/utils/anvil_metadata_utils.py
@@ -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')),
)
@@ -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,
@@ -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:
diff --git a/seqr/views/utils/test_utils.py b/seqr/views/utils/test_utils.py
index 660d3d1482..7901559819 100644
--- a/seqr/views/utils/test_utils.py
+++ b/seqr/views/utils/test_utils.py
@@ -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',
diff --git a/ui/shared/components/panel/variants/FamilyVariantTags.jsx b/ui/shared/components/panel/variants/FamilyVariantTags.jsx
index 40b4b62ee7..9adede7048 100644
--- a/ui/shared/components/panel/variants/FamilyVariantTags.jsx
+++ b/ui/shared/components/panel/variants/FamilyVariantTags.jsx
@@ -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'
@@ -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: (
-
- ),
- 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]
@@ -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 ? (
@@ -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
@@ -367,6 +359,7 @@ FamilyVariantTags.propTypes = {
dispatchUpdateFamilyVariantFunctionalTags: PropTypes.func.isRequired,
mmeSubmissionsByGuid: PropTypes.object,
genesById: PropTypes.object,
+ user: PropTypes.object,
}
FamilyVariantTags.defaultProps = {
@@ -386,6 +379,7 @@ const mapStateToProps = (state, ownProps) => {
variantTagNotes: ((getVariantTagNotesByFamilyVariants(state) || {})[ownProps.familyGuid] || {})[variantId],
mmeSubmissionsByGuid: getMmeSubmissionsByGuid(state),
genesById: getGenesById(state),
+ user: getUser(state),
}
}