diff --git a/HISTORY.rst b/HISTORY.rst
index aa84a7b23..33895b20d 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -39,6 +39,7 @@ End-User Summary
- Improving performance of case listing (#304)
- Adding shortcut buttons to phenotype annotation (#289)
- Fixing issue with multiple added variants (#283)
+- Implementing several usability improvements for clinvar submission editor (#286).
Full Change List
================
@@ -79,6 +80,7 @@ Full Change List
- Improving performance of case listing (#304)
- Adding shortcut buttons to phenotype annotation (#289)
- Fixing issue with multiple added variants (#283)
+- Implementing several usability improvements for clinvar submission editor (#286).
-------
v0.23.9
diff --git a/varfish/vueapp/src/components/SubmissionList.vue b/varfish/vueapp/src/components/SubmissionList.vue
index df45f87a8..7f78dceb9 100644
--- a/varfish/vueapp/src/components/SubmissionList.vue
+++ b/varfish/vueapp/src/components/SubmissionList.vue
@@ -20,9 +20,10 @@
@click="onListItemClicked(item.sodar_uuid)"
>
{{ getSubmissionLabel(item) }}
+ ({{ getSubmissionIndividualsLabel(item) }})
-
+
@@ -41,68 +42,89 @@
- Create a new submission by selecting one of the variants below or
-
-
- create empty.
-
+ Create new submissions by selecting from the variants below and clicking .
+ If you select no variant, a blank submission will be created.
-
- Filters:
-
-
- all:
-
-
- comments
-
-
- candidates
-
-
- causative
-
-
- VUCS3
-
-
- LP4
-
-
- P5
-
+
-
+
+
{{ getVariantLabel(item) }}
{{ getVariantExtraLabel(item) }}
@@ -116,7 +138,8 @@
}"
:title="`${item.comments.length} user comments`"
>
- {{ item.comments.length }}
+
+ {{ item.comments.length }}
|
- There is no user annotation for variants in this project.
+ There is no matching user annotation for variants in this project.
@@ -165,7 +188,7 @@
import { mapActions, mapState } from 'vuex'
import draggable from 'vuedraggable'
import SubmissionEditor from './SubmissionEditor'
-import { getSubmissionLabel, validConfirmed } from '@/helpers'
+import { isDiseaseTerm, getSubmissionLabel, validConfirmed, removeItemAll, HPO_INHERITANCE_MODE, HPO_AGE_OF_ONSET } from '@/helpers'
export default {
components: { draggable, SubmissionEditor },
@@ -177,12 +200,16 @@ export default {
modalIncludeFinalCausatives: true,
modalIncludeAcmg3: false,
modalIncludeAcmg4: true,
- modalIncludeAcmg5: true
+ modalIncludeAcmg5: true,
+ individualFilter: '',
+ onlyAddAffected: true,
+ selectedSmallVariants: []
}
},
computed: {
...mapState({
individuals: state => state.clinvarExport.individuals,
+ submissionIndividuals: state => state.clinvarExport.submissionIndividuals,
submissions: state => state.clinvarExport.submissions,
currentSubmissionSet: state => state.clinvarExport.currentSubmissionSet,
currentSubmission: state => state.clinvarExport.currentSubmission,
@@ -216,6 +243,11 @@ export default {
return { ...smallVar, flags, rating, comments }
})
.filter(smallVar => {
+ if (this.individualFilter) {
+ if (!smallVar.caseNames.some(s => s.includes(this.individualFilter))) {
+ return false
+ }
+ }
if (this.modalIncludeAll) {
return true
} else if (this.modalIncludeComments && smallVar.comments.length > 0) {
@@ -252,6 +284,25 @@ export default {
getSubmissionLabel,
validConfirmed,
+ getSubmissionIndividualsCount (item) {
+ return item.submission_individuals.length
+ },
+ getSubmissionIndividualsLabel (item) {
+ let names = item.submission_individuals.map(
+ uuid => this.individuals[this.submissionIndividuals[uuid].individual].name.replace(/-N.-DNA.-....$/, '')
+ )
+ if (names.length > 2) {
+ names = names.slice(0, 2) + ['...']
+ }
+ return names.join(', ')
+ },
+ isVariantSelected (item) {
+ const variantDesc = this.getVariantDesc(item)
+ return this.selectedSmallVariants.includes(variantDesc)
+ },
+ getVariantDesc (item) {
+ return `${item.release}-${item.chromosome}-${item.start}-${item.reference}-${item.alternative}`
+ },
getVariantLabel (item) {
return `${item.refseq_gene_symbol}:${item.refseq_hgvs_p || ''}`
},
@@ -279,19 +330,34 @@ export default {
this.selectCurrentSubmission(item)
})
},
- onCreateEmptySubmissionClicked () {
- this.createSubmissionInCurrentSubmissionSet({
- smallVariant: null,
- submission: this.getEmptySubmissionData(),
- individualUuids: []
- })
- this.$bvModal.hide('modal-add-submission')
+ onVariantClicked (smallVariant) {
+ const variantDesc = this.getVariantDesc(smallVariant)
+ if (this.selectedSmallVariants.includes(variantDesc)) {
+ removeItemAll(this.selectedSmallVariants, variantDesc)
+ } else {
+ this.selectedSmallVariants.push(variantDesc)
+ }
},
/**
* Clicked on an existing small variant with user annotation.
*/
- onCreateSubmissionClicked (smallVariant) {
- this.createSubmissionInCurrentSubmissionSet(this.getSubmissionData(smallVariant))
+ onCreateSubmissionClicked () {
+ if (!this.selectedSmallVariants.length) {
+ this.createSubmissionInCurrentSubmissionSet({
+ smallVariant: null,
+ submission: this.getEmptySubmissionData(),
+ individualUuids: []
+ })
+ } else {
+ for (let i = 0; i < this.modalUserAnnotations.length; ++i) {
+ const smallVariant = this.modalUserAnnotations[i]
+ const variantDesc = this.getVariantDesc(smallVariant)
+ if (this.selectedSmallVariants.includes(variantDesc)) {
+ this.createSubmissionInCurrentSubmissionSet(this.getSubmissionData(smallVariant))
+ }
+ }
+ this.selectedSmallVariants = []
+ }
this.$bvModal.hide('modal-add-submission')
},
onAddSubmissionClicked () {
@@ -305,10 +371,14 @@ export default {
*/
getSubmissionData (smallVariant) {
// Get individuals that carry the variants.
+ const affectedNames = Object.values(this.individuals)
+ .filter(indiv => indiv.affected === 'yes')
+ .map(indiv => indiv.name)
const carrierNames = Object.entries(smallVariant.genotype)
.filter(kv => {
+ const name = kv[0]
const value = kv[1]
- return value.gt && value.gt.includes('1')
+ return value.gt && value.gt.includes('1') && (!this.onlyAddAffected || affectedNames.includes(name))
})
.map(kv => kv[0])
const individualUuids = Object.entries(this.individuals)
@@ -327,6 +397,26 @@ export default {
const variantGene = [smallVariant.refseq_gene_symbol]
const variantHgvs = [smallVariant.refseq_hgvs_p || 'p.?']
+ let ageOfOnset = ''
+ let inheritance = ''
+ const diseases = []
+ for (const individualUuid of individualUuids) {
+ const individual = this.individuals[individualUuid]
+ if (individual.phenotype_terms) {
+ // eslint-disable-next-line camelcase
+ for (let { term_id, term_name } of individual.phenotype_terms) {
+ // eslint-disable-next-line camelcase
+ term_name = term_name.split(';')[0].trim()
+ inheritance = inheritance || HPO_INHERITANCE_MODE.get(term_id) || ''
+ ageOfOnset = ageOfOnset || HPO_AGE_OF_ONSET.get(term_id) || ''
+ // eslint-disable-next-line camelcase
+ if (isDiseaseTerm(term_id) && !diseases.some(x => x.term_id === term_id)) {
+ diseases.push({ term_id, term_name })
+ }
+ }
+ }
+ }
+
const submission = {
record_status: 'novel',
release_status: 'public',
@@ -334,8 +424,9 @@ export default {
significance_description: significanceDescription,
significance_last_evaluation: (new Date()).toISOString().substr(0, 10),
assertion_method: Object.values(this.assertionMethods)[0].sodar_uuid,
- age_of_onset: '',
- inheritance: '',
+ age_of_onset: ageOfOnset,
+ diseases: diseases,
+ inheritance: inheritance,
variant_type: 'Variation',
variant_assembly: smallVariant.release,
variant_chromosome: smallVariant.chromosome,
@@ -398,4 +489,12 @@ export default {
.cursor-pointer {
cursor: pointer;
}
+
+.x-not-active .x-active-show {
+ display: none;
+}
+
+.x-active .x-active-hide {
+ display: none;
+}
diff --git a/varfish/vueapp/src/helpers.js b/varfish/vueapp/src/helpers.js
index 5a305a642..b3541dc1b 100644
--- a/varfish/vueapp/src/helpers.js
+++ b/varfish/vueapp/src/helpers.js
@@ -18,6 +18,26 @@ export function getSubmissionLabel (item) {
}
}
+export function removeItemOnce (arr, value) {
+ const index = arr.indexOf(value)
+ if (index > -1) {
+ arr.splice(index, 1)
+ }
+ return arr
+}
+
+export function removeItemAll (arr, value) {
+ let i = 0
+ while (i < arr.length) {
+ if (arr[i] === value) {
+ arr.splice(i, 1)
+ } else {
+ ++i
+ }
+ }
+ return arr
+}
+
/**
* Check current form for valid and display message or execute callback.
*
@@ -37,3 +57,54 @@ export function validConfirmed (cb, message = 'Please fix the problems first.',
return true
}
}
+
+export function isDiseaseTerm (termId) {
+ return termId.startsWith('OMIM:') || termId.startsWith('ORPHA:')
+}
+
+export const HPO_INHERITANCE_MODE = Object.freeze(new Map([
+ ['HP:0001452', 'Autosomal dominant contiguous gene syndrome'],
+ ['HP:0025352', 'Autosomal dominant germline de novo mutation'],
+ ['HP:0000006', 'Autosomal dominant inheritance'],
+ ['HP:0012275', 'Autosomal dominant inheritance with maternal imprinting'],
+ ['HP:0012274', 'Autosomal dominant inheritance with paternal imprinting'],
+ ['HP:0001444', 'Autosomal dominant somatic cell mutation'],
+ ['HP:0000007', 'Autosomal recessive inheritance'],
+ ['HP:0001466', 'Contiguous gene syndrome'],
+ ['HP:0010984', 'Digenic inheritance'],
+ ['HP:0003743', 'Genetic anticipation'],
+ ['HP:0003744', 'Genetic anticipation with paternal anticipation bias'],
+ ['HP:0010985', 'Gonosomal inheritance'],
+ ['HP:0001475', 'Male-limited autosomal dominant'],
+ ['HP:0001427', 'Mitochondrial inheritance'],
+ ['HP:0001426', 'Multifactorial inheritance'],
+ ['HP:0010983', 'Oligogenic inheritance'],
+ ['HP:0010982', 'Polygenic inheritance'],
+ ['HP:0032113', 'Semidominant mode of inheritance'],
+ ['HP:0001470', 'Sex-limited autosomal dominant'],
+ ['HP:0031362', 'Sex-limited autosomal recessive inheritance'],
+ ['HP:0001442', 'Somatic mosaicism'],
+ ['HP:0001428', 'Somatic mutation'],
+ ['HP:0003745', 'Sporadic'],
+ ['HP:0032382', 'Uniparental disomy'],
+ ['HP:0032383', 'Uniparental heterodisomy'],
+ ['HP:0032384', 'Uniparental isodisomy'],
+ ['HP:0001423', 'X-linked dominant inheritance'],
+ ['HP:0001417', 'X-linked inheritance'],
+ ['HP:0001419', 'X-linked recessive inheritance']
+]))
+export const HPO_AGE_OF_ONSET = Object.freeze(new Map([
+ ['HP:0030674', 'Antenatal'],
+ ['HP:0011460', 'Embryonal'],
+ ['HP:0011461', 'Fetal'],
+ ['HP:0410280', 'Pediatric'],
+ ['HP:0003593', 'Infantile'],
+ ['HP:0011405', 'Childhood'],
+ ['HP:0003621', 'Juvenile'],
+ ['HP:0003581', 'Adult'],
+ ['HP:0003623', 'Neonatal'],
+ ['HP:0011462', 'Young adult'],
+ ['HP:0003596', 'Middle age'],
+ ['HP:0003584', 'Late'],
+ ['HP:0003577', 'Congenital']
+]))
diff --git a/varfish/vueapp/src/store/modules/clinvarExport.js b/varfish/vueapp/src/store/modules/clinvarExport.js
index 529eec05d..2e5e2084d 100644
--- a/varfish/vueapp/src/store/modules/clinvarExport.js
+++ b/varfish/vueapp/src/store/modules/clinvarExport.js
@@ -1,6 +1,6 @@
import Vue from 'vue'
import clinvarExport from '../../api/clinvarExport'
-import { uuidv4 } from '@/helpers'
+import { uuidv4, isDiseaseTerm, HPO_INHERITANCE_MODE } from '@/helpers'
/**
* Enum for the valid clinvar export application states.
@@ -103,10 +103,17 @@ const actions = {
* Changes will be committed through `wizardSave`.
*/
createNewSubmissionSet ({ state, commit }) {
+ const titles = Object.values(state.submissionSets).map(submissionSet => submissionSet.title)
+ let title = 'New Submission Set'
+ let i = 2
+ while (titles.includes(title)) {
+ title = 'New Submission Set #' + i
+ i += 1
+ }
const submissionSet = {
sodar_uuid: uuidv4(),
date_modified: new Date().toLocaleString(),
- title: 'New Submission',
+ title: title,
state: 'draft',
sort_order: Object.keys(state.submissionSets).length,
submitter: null,
@@ -117,6 +124,7 @@ const actions = {
commit('ADD_SUBMISSION_SET', submissionSet)
commit('SET_CURRENT_SUBMISSION_SET', submissionSet.sodar_uuid)
+ commit('SET_WIZARD_STATE', WizardState.submissionSet)
commit('SET_APP_STATE', AppState.add)
},
/**
@@ -334,10 +342,12 @@ const actions = {
commit('SET_APP_STATE', AppState.list)
for (const submittingOrgUuid of state.currentSubmissionSet.submitting_orgs) {
- await clinvarExport.deleteSubmittingOrg(
- state.submittingOrgs[submittingOrgUuid],
- state.appContext
- )
+ if (submittingOrgUuid in state.oldModel.submittingOrgs) {
+ await clinvarExport.deleteSubmittingOrg(
+ state.submittingOrgs[submittingOrgUuid],
+ state.appContext
+ )
+ }
commit('DELETE_SUBMITTING_ORG', submittingOrgUuid)
}
@@ -345,23 +355,29 @@ const actions = {
for (const submissionUuid of submissionUuids) {
const submissionInvidualUuids = Array.from(state.submissions[submissionUuid].submission_individuals)
for (const submissionInvidualUuid of submissionInvidualUuids) {
- await clinvarExport.deleteSubmissionIndividual(
- state.submissionIndividuals[submissionInvidualUuid],
+ if (submissionInvidualUuid in state.oldModel.submissionIndividuals) {
+ await clinvarExport.deleteSubmissionIndividual(
+ state.submissionIndividuals[submissionInvidualUuid],
+ state.appContext
+ )
+ }
+ commit('DELETE_SUBMISSION_INDIVIDUAL', submissionInvidualUuid)
+ }
+ if (submissionUuid in state.oldModel.submissions) {
+ await clinvarExport.deleteSubmission(
+ state.submissions[submissionUuid],
state.appContext
)
- commit('DELETE_SUBMISSION_INDIVIDUAL', submissionInvidualUuid)
}
- await clinvarExport.deleteSubmission(
- state.submissions[submissionUuid],
- state.appContext
- )
commit('DELETE_SUBMISSION', submissionUuid)
}
- await clinvarExport.deleteSubmissionSet(
- state.submissionSets[state.currentSubmissionSet.sodar_uuid],
- state.appContext
- )
+ if (state.currentSubmissionSet.sodar_uuid in state.oldModel.submissionSets) {
+ await clinvarExport.deleteSubmissionSet(
+ state.submissionSets[state.currentSubmissionSet.sodar_uuid],
+ state.appContext
+ )
+ }
commit('DELETE_SUBMISSION_SET', state.currentSubmissionSet.sodar_uuid)
commit('SET_CURRENT_SUBMISSION_SET', null)
@@ -510,21 +526,49 @@ function sodarObjectListToObject (lst) {
* Extract variant zygosity information from state for the given smallVariant.
*/
function extractVariantZygosity (smallVariant, individualUuids, state) {
- let variantAlleleCount = 0
+ function getVariantZygosity (variantAlleleCount, isRecessive) {
+ if (variantAlleleCount === 2) {
+ return 'Homozygote'
+ } else {
+ if (isRecessive) {
+ return 'Compound heterozygote'
+ } else {
+ return 'Single heterozygote'
+ }
+ }
+ }
+
+ // See whether any individual is annotated as recessive.
+ let anyRecessive = false
+ let variantAlleleCount = null
let variantZygosity = null
if (smallVariant !== null) {
let individual = null
for (const individualUuid of individualUuids) {
- individual = state.individuals[individualUuid]
- if (individual.name in smallVariant.genotype) {
+ const currIndividual = state.individuals[individualUuid]
+ if (individual === null) {
+ individual = currIndividual
+ }
+ console.log('A')
+ if (variantAlleleCount === null && (individual.name in smallVariant.genotype)) {
variantAlleleCount = ((smallVariant.genotype[individual.name].gt || '').match(/1/g) || []).length
- break
}
+ console.log('B', individual)
+ if (individual !== null) {
+ // eslint-disable-next-line camelcase
+ for (const { term_id } of (individual.phenotype_terms || [])) {
+ anyRecessive = anyRecessive || (HPO_INHERITANCE_MODE.get(term_id) || '').includes('recessive')
+ }
+ }
+ console.log('C')
+ }
+ if (variantAlleleCount === null) {
+ variantAlleleCount = 0
}
if (individual && variantAlleleCount) {
if (smallVariant.chromosome.includes('X')) {
if (individual.sex === 'female') {
- variantZygosity = (variantAlleleCount === 2) ? 'Homozygote' : 'Single heterozygote'
+ variantZygosity = getVariantZygosity(variantAlleleCount, anyRecessive)
} else {
variantAlleleCount = 1
variantZygosity = 'Hemizygote'
@@ -538,7 +582,7 @@ function extractVariantZygosity (smallVariant, individualUuids, state) {
variantZygosity = 'Hemizygote'
}
} else {
- variantZygosity = (variantAlleleCount === 2) ? 'Homozygote' : 'Single heterozygote'
+ variantZygosity = getVariantZygosity(variantAlleleCount, anyRecessive)
}
}
}
@@ -867,8 +911,8 @@ const mutations = {
phenotypes: JSON.parse(JSON.stringify(state.individuals[individual.sodar_uuid].phenotype_terms)),
variant_zygosity: variantZygosity,
variant_allele_count: variantAlleleCount,
- variant_origin: 'not provided',
- source: 'not provided',
+ variant_origin: 'germline',
+ source: 'clinical testing',
tissue: 'Blood',
citations: []
}
@@ -881,7 +925,6 @@ const mutations = {
// when sent to the UUID so it must be then updated locally.
const newSubmission = {
...submission,
-
_isInvalid: false,
sodar_uuid: uuidv4(),
sort_order: Object.keys(state.submissions).length,
@@ -891,15 +934,17 @@ const mutations = {
for (const individualUuid of individualUuids) {
const individual = state.individuals[individualUuid]
+ const phenotypes = JSON.parse(JSON.stringify(individual.phenotype_terms || []))
+ .filter(term => !HPO_INHERITANCE_MODE.has(term.term_id) && !isDiseaseTerm(term.term_id))
const newSubmissionIndividual = {
sodar_uuid: uuidv4(),
individual: individualUuid,
submission: newSubmission.sodar_uuid,
- phenotypes: JSON.parse(JSON.stringify(individual.phenotype_terms || [])),
+ phenotypes: phenotypes,
variant_allele_count: variantAlleleCount,
variant_zygosity: variantZygosity,
- variant_origin: 'not provided',
- source: 'not provided',
+ variant_origin: 'germline',
+ source: 'clinical testing',
tissue: 'Blood',
citations: []
}