- Try searching for variants using HGVS strings like
- ENST00000473961.6:c.-19-2A>T
- and
- NP_000242.1:p.Asn566Thr. MaveDB supports a variety of HGVS formats for searching. Or browse these curated data sets:
-
Gene
@@ -152,7 +170,7 @@
-
+
@@ -331,10 +349,10 @@ import {useHead} from '@unhead/vue'
import config from '@/config'
import DefaultLayout from '@/components/layout/DefaultLayout.vue'
-import {MAVE_MD_SCORE_SETS} from '@/lib/mavemd'
+import {clinGenAlleleIdRegex, clinVarVariationIdRegex, rsIdRegex, MAVE_MD_SCORE_SETS} from '@/lib/mavemd'
import {components} from '@/schema/openapi'
import {getScoreSetShortName} from '@/lib/score-sets'
-import {clinVarHgvsSearchStringRegex} from '@/lib/mave-hgvs'
+import {clinVarHgvsSearchStringRegex, hgvsSearchStringRegex} from '@/lib/mave-hgvs'
const SCORE_SETS_TO_SHOW = 5
@@ -368,10 +386,18 @@ export default defineComponent({
data: function () {
return {
loading: false,
- hgvsSearchVisible: true,
+ defaultSearchVisible: true,
+ searchSuggestionsVisible: false,
fuzzySearchVisible: false,
searchResultsVisible: false,
searchText: null as string | null,
+ searchType: null as string | null,
+ searchTypeOptions: [
+ {code: 'hgvs', name: 'HGVS', examples: ['ENST00000473961.6:c.-19-2A>T', 'NP_000242.1:p.Asn566Thr']},
+ {code: 'clinGenAlleleId', name: 'ClinGen Allele ID', examples: ['CA10590195', 'PA2579983208']},
+ {code: 'dbSnpRsId', name: 'dbSNP rsID', examples: ['rs900082291', '900082291']},
+ {code: 'clinVarVariationId', name: 'ClinVar Variation ID', examples: ['869058']},
+ ],
inputGene: null as string | null,
inputVariantType: null as string | null,
inputVariantPosition: null as string | null,
@@ -461,6 +487,15 @@ export default defineComponent({
}
}
},
+ searchType: {
+ handler(newVal, oldVal) {
+ if (newVal !== oldVal) {
+ this.router.replace({
+ query: {search: this.searchText || undefined, searchType: newVal}
+ })
+ }
+ }
+ },
'$route.query.search': {
immediate: true,
handler(newVal) {
@@ -471,6 +506,16 @@ export default defineComponent({
}
}
},
+ '$route.query.searchType': {
+ immediate: true,
+ handler(newVal) {
+ if (typeof newVal === 'string') {
+ this.searchType = newVal
+ } else if (!newVal) {
+ this.searchType = 'hgvs'
+ }
+ }
+ },
'$route.query.gene': {
immediate: true,
handler(newVal) {
@@ -504,11 +549,11 @@ export default defineComponent({
},
mounted() {
- // If HGVS search param is present, run HGVS search
+ // If search param is present, run default search
if (this.route.query.search && String(this.route.query.search).trim() !== '') {
- this.hgvsSearchVisible = true
+ this.defaultSearchVisible = true
this.fuzzySearchVisible = false
- this.hgvsSearch()
+ this.defaultSearch()
} else if (
this.route.query.gene ||
this.route.query.variantType ||
@@ -517,7 +562,7 @@ export default defineComponent({
this.route.query.altAllele
) {
// If any fuzzy search param is present, run fuzzy search
- this.hgvsSearchVisible = false
+ this.defaultSearchVisible = false
this.fuzzySearchVisible = true
this.fuzzySearch()
}
@@ -551,13 +596,15 @@ export default defineComponent({
toggleGuide: function () {
this.guideExpanded = !this.guideExpanded
},
- searchForText: function (event: PointerEvent) {
+ searchForText: function (event: PointerEvent, searchType: string) {
const element = event.target
+ this.searchType = searchType
if (element) {
- this.showSearch('hgvs')
+ this.showSearch('default')
this.searchText = element.innerText
- this.hgvsSearch()
+ this.defaultSearch()
}
+ this.router.replace({query: {searchType: this.searchType, search: this.searchText}})
},
clearSearch() {
this.searchText = null
@@ -569,11 +616,13 @@ export default defineComponent({
this.searchResultsVisible = false
this.alleles = []
},
- showSearch(searchType: 'fuzzy' | 'hgvs' = 'hgvs') {
- this.hgvsSearchVisible = searchType == 'hgvs'
- this.fuzzySearchVisible = searchType == 'fuzzy'
+ showSearch(searchMethod: 'fuzzy' | 'default' = 'default') {
+
+ this.defaultSearchVisible = searchMethod == 'default'
+ this.fuzzySearchVisible = searchMethod == 'fuzzy'
+
},
- hgvsSearch: async function () {
+ defaultSearch: async function () {
this.searchResultsVisible = true
// Remove fuzzy search params from the URL
const {gene, variantType, variantPosition, refAllele, altAllele, ...rest} = this.route.query
@@ -583,158 +632,226 @@ export default defineComponent({
this.alleles = []
this.loading = true
if (this.searchText !== null && this.searchText !== '') {
- await this.fetchHgvsSearchResults(this.searchText)
+ await this.fetchDefaultSearchResults(this.searchText)
}
this.loading = false
await this.searchVariants()
},
- fetchHgvsSearchResults: async function (hgvsSearch: string, maneStatus: string | null = null) {
- // Strip gene symbol and/or protein consequence from ClinVar-style variant names to obtain valid HGVS. If the
- // search string doesn't match the pattern, leave it as is and let the rest of the search code handle any format
- // problem.
- const match = clinVarHgvsSearchStringRegex.exec(hgvsSearch.trim())
- const hgvsStr = match ? `${match.groups!.identifier}:${match.groups!.description}` : hgvsSearch.trim()
+ fetchDefaultSearchResults: async function (searchString: string, maneStatus: string | null = null) {
+ const searchType = this.searchType
+ let searchStr = searchString.trim()
try {
- const response = await axios.get('https://reg.test.genome.network/allele', {
- params: {
- hgvs: hgvsStr
+ let response
+ if (searchType === 'clinGenAlleleId') {
+ if (!clinGenAlleleIdRegex.test(searchStr)) {
+ this.toast.add({
+ severity: 'error',
+ summary: 'Invalid search',
+ detail: `Please provide a valid ClinGen Allele ID (e.g. ${_.join(_.find(this.searchTypeOptions, { code: searchType })?.examples, ', ')})`,
+ life: 10000
+ })
+ return
}
- })
- const clingenAlleleId = response.data?.['@id']?.split('/')?.at(-1)
- if (clingenAlleleId && clingenAlleleId.startsWith('CA')) {
- const newAllele = {
- clingenAlleleUrl: response.data?.['@id'],
- clingenAlleleId: clingenAlleleId,
- canonicalAlleleName: response.data?.communityStandardTitle?.[0] || undefined,
- maneStatus: maneStatus,
- genomicAlleles: response.data?.genomicAlleles || [],
- grch38Hgvs: null,
- grch37Hgvs: null,
- transcriptAlleles: response.data?.transcriptAlleles || [],
- maneCoordinates: [] as Array,
- variantsStatus: 'NotLoaded',
- variants: {
- nucleotide: [] as Array,
- protein: [] as Array,
- associatedNucleotide: [] as Array
+ response = await axios.get(`https://reg.genome.network/allele/${searchStr}`)
+ } else if (searchType === 'dbSnpRsId') {
+ if (!rsIdRegex.test(searchStr) && !rsIdRegex.test(`rs${searchStr}`)) {
+ this.toast.add({
+ severity: 'error',
+ summary: 'Invalid search',
+ detail: `Please provide a valid dbSNP rsID (e.g. ${_.join(_.find(this.searchTypeOptions, { code: searchType })?.examples, ', ')})`,
+ life: 10000
+ })
+ return
+ }
+ response = await axios.get('https://reg.genome.network/alleles', {
+ params: {
+ 'dbSNP.rs': searchStr
}
+ })
+ } else if (searchType === 'clinVarVariationId') {
+ if (!clinVarVariationIdRegex.test(searchStr)) {
+ this.toast.add({
+ severity: 'error',
+ summary: 'Invalid search',
+ detail: `Please provide a valid ClinVar Variation ID (e.g. ${_.join(_.find(this.searchTypeOptions, { code: searchType })?.examples, ', ')})`,
+ life: 10000
+ })
+ return
}
- for (let i = 0; i < newAllele.genomicAlleles.length; i++) {
- // TODO currently just taking first entry from hgvs array, since that appears to be NC coordinates. check this assumption
- if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh38') {
- newAllele.grch38Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
- } else if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh37') {
- newAllele.grch37Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
+ response = await axios.get('https://reg.genome.network/alleles', {
+ params: {
+ 'ClinVar.variationId': searchStr
}
+ })
+ } else {
+ if (!hgvsSearchStringRegex.test(searchStr)) {
+ this.toast.add({
+ severity: 'error',
+ summary: 'Invalid search',
+ detail: `Please provide a valid HGVS string (e.g. ${_.join(_.find(this.searchTypeOptions, { code: searchType })?.examples, ', ')})`,
+ life: 10000
+ })
+ return
}
- for (let i = 0; i < newAllele.transcriptAlleles.length; i++) {
- if (newAllele.transcriptAlleles[i].MANE !== undefined) {
- // TODO may want to prioritize one of MANE select, MANE clinical, etc. For now, just grab the first mane transcript.
- const mane = newAllele.transcriptAlleles[i].MANE
- for (const sequenceType of ['nucleotide', 'protein']) {
- for (const database in mane[sequenceType]) {
- newAllele.maneCoordinates.push({
- sequenceType: sequenceType,
- database: database,
- hgvs: mane[sequenceType][database].hgvs
- })
+ // Strip gene symbol and/or protein consequence from ClinVar-style variant names to obtain valid HGVS. If the
+ // search string doesn't match the pattern, leave it as is and let the rest of the search code handle any format
+ // problem.
+ const match = clinVarHgvsSearchStringRegex.exec(searchStr)
+ if (match) {
+ searchStr = `${match.groups!.identifier}:${match.groups!.description}`
+ }
+ response = await axios.get('https://reg.genome.network/allele', {
+ params: {
+ hgvs: searchStr
+ }
+ })
+ }
+ // determine if response data is a single allele or a list of alleles
+ const results = Array.isArray(response.data) ? response.data : [response.data]
+
+ for (const result of results) {
+ const clingenAlleleId = result['@id']?.split('/')?.at(-1)
+ if (clingenAlleleId && clingenAlleleId.startsWith('CA')) {
+ const newAllele = {
+ clingenAlleleUrl: result?.['@id'],
+ clingenAlleleId: clingenAlleleId,
+ canonicalAlleleName: result?.communityStandardTitle?.[0] || undefined,
+ maneStatus: maneStatus,
+ genomicAlleles: result?.genomicAlleles || [],
+ grch38Hgvs: null,
+ grch37Hgvs: null,
+ transcriptAlleles: result?.transcriptAlleles || [],
+ maneCoordinates: [] as Array,
+ variantsStatus: 'NotLoaded',
+ variants: {
+ nucleotide: [] as Array,
+ protein: [] as Array,
+ associatedNucleotide: [] as Array
+ }
+ }
+ for (let i = 0; i < newAllele.genomicAlleles.length; i++) {
+ // TODO currently just taking first entry from hgvs array, since that appears to be NC coordinates. check this assumption
+ if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh38') {
+ newAllele.grch38Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
+ } else if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh37') {
+ newAllele.grch37Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
+ }
+ }
+ for (let i = 0; i < newAllele.transcriptAlleles.length; i++) {
+ if (newAllele.transcriptAlleles[i].MANE !== undefined) {
+ // TODO may want to prioritize one of MANE select, MANE clinical, etc. For now, just grab the first mane transcript.
+ const mane = newAllele.transcriptAlleles[i].MANE
+ for (const sequenceType of ['nucleotide', 'protein']) {
+ for (const database in mane[sequenceType]) {
+ newAllele.maneCoordinates.push({
+ sequenceType: sequenceType,
+ database: database,
+ hgvs: mane[sequenceType][database].hgvs
+ })
+ }
}
}
+ break
}
- break
- }
- this.alleles.push(newAllele)
- } else if (clingenAlleleId && clingenAlleleId.startsWith('PA')) {
- // Surface result on nucleotide-level variant if possible
- // some PA IDs do not have associated CA IDs/matching registered transcripts
- // because they are single amino acid variants caused by multi-nucleotide variants
- // or delins nucleotide variants, and the more complex nucleotide variant has not
- // been registered with ClinGen yet. in this case, use the PA ID
+ this.alleles.push(newAllele)
+ } else if (clingenAlleleId && clingenAlleleId.startsWith('PA')) {
+ // Surface result on nucleotide-level variant if possible
+ // some PA IDs do not have associated CA IDs/matching registered transcripts
+ // because they are single amino acid variants caused by multi-nucleotide variants
+ // or delins nucleotide variants, and the more complex nucleotide variant has not
+ // been registered with ClinGen yet. in this case, use the PA ID
- // get amino acid allele associated with the searched hgvs
- // note, not sure if we should assume that the searched hgvs will appear here.
- const aminoAcidAlleles = response.data?.aminoAcidAlleles || []
- for (let i = 0; i < aminoAcidAlleles.length; i++) {
- if (aminoAcidAlleles[i].hgvs?.includes(hgvsSearch)) {
- const transcripts = aminoAcidAlleles[i]?.matchingRegisteredTranscripts || []
- if (transcripts.length > 0) {
- for (let j = 0; j < transcripts.length; j++) {
- const associatedClingenAlleleId = transcripts[j]?.['@id']?.split('/')?.at(-1)
- const associatedResponse = await axios.get(
- `https://reg.test.genome.network/allele/${associatedClingenAlleleId}`
- )
+ // get amino acid allele associated with the searched hgvs
+ // note, not sure if we should assume that the searched hgvs will appear here.
+ const aminoAcidAlleles = result?.aminoAcidAlleles || []
+ for (let i = 0; i < aminoAcidAlleles.length; i++) {
+ if (searchType !== 'hgvs' || aminoAcidAlleles[i].hgvs?.includes(searchString)) {
+ const transcripts = aminoAcidAlleles[i]?.matchingRegisteredTranscripts || []
+ if (transcripts.length > 0) {
+ for (let j = 0; j < transcripts.length; j++) {
+ const associatedClingenAlleleId = transcripts[j]?.['@id']?.split('/')?.at(-1)
+ const associatedResponse = await axios.get(
+ `https://reg.genome.network/allele/${associatedClingenAlleleId}`
+ )
+ const newAllele = {
+ clingenAlleleUrl: associatedResponse.data?.['@id'],
+ clingenAlleleId: associatedResponse.data?.['@id']?.split('/')?.at(-1),
+ canonicalAlleleName: associatedResponse.data?.communityStandardTitle?.[0] || undefined,
+ maneStatus: maneStatus,
+ genomicAlleles: associatedResponse.data?.genomicAlleles || [],
+ grch38Hgvs: null,
+ grch37Hgvs: null,
+ transcriptAlleles: associatedResponse.data?.transcriptAlleles || [],
+ maneCoordinates: [] as Array,
+ variantsStatus: 'NotLoaded',
+ variants: {
+ nucleotide: [] as Array,
+ protein: [] as Array,
+ associatedNucleotide: [] as Array
+ }
+ }
+ for (let i = 0; i < newAllele.genomicAlleles.length; i++) {
+ // TODO currently just taking first entry from hgvs array, since that appears to be NC coordinates. check this assumption
+ if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh38') {
+ newAllele.grch38Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
+ } else if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh37') {
+ newAllele.grch37Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
+ }
+ }
+ for (let i = 0; i < newAllele.transcriptAlleles.length; i++) {
+ if (newAllele.transcriptAlleles[i].MANE !== undefined) {
+ // TODO may want to prioritize one of MANE select, MANE clinical, etc. For now, just grab the first mane transcript.
+ const mane = newAllele.transcriptAlleles[i].MANE
+ for (const sequenceType of ['nucleotide', 'protein']) {
+ for (const database in mane[sequenceType]) {
+ newAllele.maneCoordinates.push({
+ sequenceType: sequenceType,
+ database: database,
+ hgvs: mane[sequenceType][database].hgvs
+ })
+ }
+ }
+ }
+ break
+ }
+ this.alleles.push(newAllele)
+ }
+ } else {
+ // no associated CA IDs, use PA ID as search result
+ // there is not as much info available for PA IDs
const newAllele = {
- clingenAlleleUrl: associatedResponse.data?.['@id'],
- clingenAlleleId: associatedResponse.data?.['@id']?.split('/')?.at(-1),
- canonicalAlleleName: associatedResponse.data?.communityStandardTitle?.[0] || undefined,
- maneStatus: maneStatus,
- genomicAlleles: associatedResponse.data?.genomicAlleles || [],
- grch38Hgvs: null,
- grch37Hgvs: null,
- transcriptAlleles: associatedResponse.data?.transcriptAlleles || [],
- maneCoordinates: [] as Array,
+ clingenAlleleUrl: result?.['@id'],
+ clingenAlleleId: result?.['@id']?.split('/')?.at(-1),
+ canonicalAlleleName: searchStr, // since we have already determined a match, just use supplied search string as a name
variantsStatus: 'NotLoaded',
variants: {
nucleotide: [] as Array,
protein: [] as Array,
associatedNucleotide: [] as Array
- }
- }
- for (let i = 0; i < newAllele.genomicAlleles.length; i++) {
- // TODO currently just taking first entry from hgvs array, since that appears to be NC coordinates. check this assumption
- if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh38') {
- newAllele.grch38Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
- } else if (newAllele.genomicAlleles[i].referenceGenome === 'GRCh37') {
- newAllele.grch37Hgvs = newAllele.genomicAlleles[i].hgvs?.[0]
- }
- }
- for (let i = 0; i < newAllele.transcriptAlleles.length; i++) {
- if (newAllele.transcriptAlleles[i].MANE !== undefined) {
- // TODO may want to prioritize one of MANE select, MANE clinical, etc. For now, just grab the first mane transcript.
- const mane = newAllele.transcriptAlleles[i].MANE
- for (const sequenceType of ['nucleotide', 'protein']) {
- for (const database in mane[sequenceType]) {
- newAllele.maneCoordinates.push({
- sequenceType: sequenceType,
- database: database,
- hgvs: mane[sequenceType][database].hgvs
- })
- }
- }
- }
- break
+ },
+ // the following fields are not available for PA IDs
+ maneStatus: null,
+ genomicAlleles: [],
+ grch38Hgvs: null,
+ grch37Hgvs: null,
+ transcriptAlleles: [],
+ maneCoordinates: [] as Array
}
this.alleles.push(newAllele)
}
- } else {
- // no associated CA IDs, use PA ID as search result
- // there is not as much info available for PA IDs
- const newAllele = {
- clingenAlleleUrl: response.data?.['@id'],
- clingenAlleleId: response.data?.['@id']?.split('/')?.at(-1),
- canonicalAlleleName: hgvsStr, // since we have already determined a match, just use supplied hgvs string as a name
- variantsStatus: 'NotLoaded',
- variants: {
- nucleotide: [] as Array,
- protein: [] as Array,
- associatedNucleotide: [] as Array
- },
- // the following fields are not available for PA IDs
- maneStatus: null,
- genomicAlleles: [],
- grch38Hgvs: null,
- grch37Hgvs: null,
- transcriptAlleles: [],
- maneCoordinates: [] as Array
- }
- this.alleles.push(newAllele)
}
+ // only expect one amino acid allele match
+ break
}
- // only expect one amino acid allele match
- break
}
}
+ if (this.alleles.length > 0) {
+ this.$refs.searchResults.scrollIntoView({
+ behavior: "smooth",
+ block: "start"
+ })
+ }
} catch (error: any) {
// NOTE: not resetting alleles here, because any error will have occurred before pushing to alleles.
// don't want to reset alleles because this function may be called in a loop to process several hgvs strings.
@@ -745,7 +862,7 @@ export default defineComponent({
error.response.data?.errorType && error.response.data?.description
? `${error.response.data?.errorType}: ${error.response.data?.description}`
: 'Error fetching results',
- detail: error.response.data?.message || 'Invalid HGVS string provided.',
+ detail: error.response.data?.message || 'Invalid search.',
life: 10000
})
}
@@ -812,7 +929,7 @@ export default defineComponent({
fuzzySearch: async function () {
this.searchResultsVisible = true
// Remove HGVS search param from the URL
- const {search, ...rest} = this.route.query
+ const {search, searchType, ...rest} = this.route.query
this.router.replace({
query: {
...rest,
@@ -847,7 +964,7 @@ export default defineComponent({
// TODO validate variant position input: can't be 0, can only include *, - (if c.) and digits
// retrieve clingen gene id
- const geneResponse = await axios.get('https://reg.test.genome.network/gene', {
+ const geneResponse = await axios.get('https://reg.genome.network/gene', {
params: {
'HGNC.symbol': geneSymbol
}
@@ -874,7 +991,7 @@ export default defineComponent({
// which could be done if variant input type is c.
// const clingenGeneId = geneResponse.data?.['@id']?.split('/')?.at(-1)
// // retrieve refseq transcripts associated with clingen allele id
- // const transcriptResponse = await axios.get('https://reg.test.genome.network/refseqs', {
+ // const transcriptResponse = await axios.get('https://reg.genome.network/refseqs', {
// params: {
// gene: clingenGeneId
// }
@@ -898,7 +1015,7 @@ export default defineComponent({
// fetch clingen allele id results for each hgvs string
for (const hgvsString of hgvsStrings) {
- await this.fetchHgvsSearchResults(hgvsString.hgvsString, hgvsString.maneStatus)
+ await this.fetchDefaultSearchResults(hgvsString.hgvsString, hgvsString.maneStatus)
}
} catch (error: any) {
this.alleles = []
@@ -909,7 +1026,7 @@ export default defineComponent({
error.response.data?.errorType && error.response.data?.description
? `${error.response.data?.errorType}: ${error.response.data?.description}`
: 'Error fetching results',
- detail: error.response.data?.message || 'Invalid HGVS string provided.',
+ detail: error.response.data?.message || 'Invalid search.',
life: 10000
})
}
@@ -989,6 +1106,16 @@ export default defineComponent({
justify-content: center;
}
+.mavedb-examples-button-container {
+ display: flex;
+ justify-content: center;
+ margin: 8px 0;
+}
+
+.mavedb-examples-button-container .p-button {
+ width: fit-content;
+}
+
.mavedb-fuzzy-search-form-component {
margin: 0 5px;
}
@@ -1012,6 +1139,11 @@ export default defineComponent({
width: 24%;
}
+.mavedb-search-suggestions {
+ display:flex;
+ justify-content: center;
+}
+
.mavedb-organism-picker:deep(.p-listbox-item) {
font-style: italic;
}
diff --git a/src/components/screens/SearchView.vue b/src/components/screens/SearchView.vue
index 9df3a520..a54e2a6a 100644
--- a/src/components/screens/SearchView.vue
+++ b/src/components/screens/SearchView.vue
@@ -18,59 +18,65 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Showing {{ publishedScoreSets.length.toLocaleString() }} of
+ {{ numTotalSearchResults.toLocaleString() }} results. Try adding more filters to narrow your search.
+
+
+}
type ScoreSetMetadataFn = (scoreSet: ShortScoreSet) => Array
-function countScoreSetMetadata(scoreSets: Array, scoreSetMetadataFn: ScoreSetMetadataFn): FilterOptions {
+function countScoreSetMetadata(
+ scoreSets: Array,
+ scoreSetMetadataFn: ScoreSetMetadataFn
+): FilterOption[] {
if (!scoreSets.length) {
return []
}
@@ -134,7 +143,7 @@ function countScoreSetMetadata(scoreSets: Array, scoreSetMetadata
.map((value) => ({value, badge: frequencies.get(value) || 0}))
}
type GeneMetadataFn = (targetGene: ShortTargetGene) => string
-function countTargetGeneMetadata(scoreSets: Array, geneMetadataFn: GeneMetadataFn): FilterOptions {
+function countTargetGeneMetadata(scoreSets: Array, geneMetadataFn: GeneMetadataFn): FilterOption[] {
return countScoreSetMetadata(scoreSets, (scoreSet) => [...new Set(scoreSet.targetGenes.map(geneMetadataFn))])
}
@@ -142,7 +151,7 @@ type PublicationMetadataFn = (publicationIdentifier: PublicationIdentifier) => A
function countPublicationMetadata(
scoreSets: Array,
publicationMetadataFn: PublicationMetadataFn
-): FilterOptions {
+): FilterOption[] {
return countScoreSetMetadata(scoreSets, (scoreSet) => {
const primary = scoreSet.primaryPublicationIdentifiers.map(publicationMetadataFn).flat()
const secondary = scoreSet.secondaryPublicationIdentifiers.map(publicationMetadataFn).flat()
@@ -185,49 +194,24 @@ export default defineComponent({
searchText: this.$route.query.search as string | null,
scoreSets: [] as Array,
publishedScoreSets: [] as Array,
+ numTotalSearchResults: 0,
language: {
emptyTable: 'Type in the search box above or use the filters to find a data set.'
},
- textForTargetGeneCategory: textForTargetGeneCategory
+ textForTargetGeneCategory: textForTargetGeneCategory,
+ targetNameFilterOptions: [] as FilterOption[],
+ targetOrganismFilterOptions: [] as FilterOption[],
+ targetAccessionFilterOptions: [] as FilterOption[],
+ targetTypeFilterOptions: [] as FilterOption[],
+ publicationAuthorFilterOptions: [] as FilterOption[],
+ publicationDatabaseFilterOptions: [] as FilterOption[],
+ publicationJournalFilterOptions: [] as FilterOption[]
}
},
computed: {
debouncedSearchFunction: function () {
return debounce(() => this.search(), '400ms')
- },
- targetNameFilterOptions: function () {
- return countTargetGeneMetadata(this.publishedScoreSets, (targetGene) => targetGene.name)
- },
- targetOrganismFilterOptions: function () {
- return countTargetGeneMetadata(
- this.publishedScoreSets,
- (targetGene) => targetGene.targetSequence?.taxonomy.organismName || ''
- )
- },
- targetAccessionFilterOptions: function () {
- return countTargetGeneMetadata(
- this.publishedScoreSets,
- (targetGene) => targetGene.targetAccession?.accession || ''
- )
- },
- targetTypeFilterOptions: function () {
- return countTargetGeneMetadata(this.publishedScoreSets, (targetGene) => targetGene.category)
- },
- publicationAuthorFilterOptions: function () {
- return countPublicationMetadata(this.publishedScoreSets, (publicationIdentifier) =>
- publicationIdentifier.authors.map((author) => author.name)
- )
- },
- publicationDatabaseFilterOptions: function () {
- return countPublicationMetadata(this.publishedScoreSets, (publicationIdentifier) =>
- publicationIdentifier.dbName ? [publicationIdentifier.dbName] : []
- )
- },
- publicationJournalFilterOptions: function () {
- return countPublicationMetadata(this.publishedScoreSets, (publicationIdentifier) =>
- publicationIdentifier.publicationJournal ? [publicationIdentifier.publicationJournal] : []
- )
}
},
@@ -421,22 +405,89 @@ export default defineComponent({
authors: this.filterPublicationAuthors.length > 0 ? this.filterPublicationAuthors : undefined,
databases: this.filterPublicationDatabases.length > 0 ? this.filterPublicationDatabases : undefined,
journals: this.filterPublicationJournals.length > 0 ? this.filterPublicationJournals : undefined,
- keywords: this.filterKeywords.length > 0 ? this.filterKeywords : undefined
+ keywords: this.filterKeywords.length > 0 ? this.filterKeywords : undefined,
+ includeExperimentScoreSetUrnsAndCount: false,
+ limit: 100
}
- let response = await axios.post(`${config.apiBaseUrl}/score-sets/search`, requestParams, {
+ const response = await axios.post(`${config.apiBaseUrl}/score-sets/search`, requestParams, {
headers: {
accept: 'application/json'
}
})
// TODO (#130) catch errors in response
- this.scoreSets = response.data || []
+ const {scoreSets, numScoreSets} = response.data || {scoreSets: [], numScoreSets: 0}
+ this.scoreSets = scoreSets
+ this.numTotalSearchResults = numScoreSets
// reset published score sets search results when using search bar
this.publishedScoreSets = this.scoreSets.filter((scoreSet) => !!scoreSet.publishedDate)
+
+ this.$nextTick(() => this.fetchFilterOptions())
} catch (err) {
console.log(`Error while loading search results")`, err)
}
},
+ fetchFilterOptions: async function () {
+ try {
+ const requestParams: SearchParams = {
+ text: this.searchText || undefined,
+ targets: this.filterTargetNames.length > 0 ? this.filterTargetNames : undefined,
+ targetOrganismNames: this.filterTargetOrganismNames.length > 0 ? this.filterTargetOrganismNames : undefined,
+ targetAccessions: this.filterTargetAccession.length > 0 ? this.filterTargetAccession : undefined,
+ targetTypes: this.filterTargetTypes.length > 0 ? this.filterTargetTypes : undefined,
+ authors: this.filterPublicationAuthors.length > 0 ? this.filterPublicationAuthors : undefined,
+ databases: this.filterPublicationDatabases.length > 0 ? this.filterPublicationDatabases : undefined,
+ journals: this.filterPublicationJournals.length > 0 ? this.filterPublicationJournals : undefined,
+ keywords: this.filterKeywords.length > 0 ? this.filterKeywords : undefined
+ }
+ const response = await axios.post(`${config.apiBaseUrl}/score-sets/search/filter-options`, requestParams, {
+ headers: {
+ accept: 'application/json'
+ }
+ })
+ // TODO (#130) catch errors in response
+ const {
+ targetAccessions,
+ targetGeneCategories,
+ targetGeneNames,
+ targetOrganismNames,
+ publicationAuthorNames,
+ publicationDbNames,
+ publicationJournals
+ } = response.data
+
+ this.targetAccessionFilterOptions = (targetAccessions || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.targetNameFilterOptions = (targetGeneNames || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.targetOrganismFilterOptions = (targetOrganismNames || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.targetTypeFilterOptions = (targetGeneCategories || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.publicationAuthorFilterOptions = (publicationAuthorNames || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.publicationDatabaseFilterOptions = (publicationDbNames || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ this.publicationJournalFilterOptions = (publicationJournals || []).map((option) => ({
+ value: option.value,
+ badge: option.count
+ }))
+ } catch (err) {
+ console.log(`Error while loading filter options")`, err)
+ }
+ },
clear: function () {
this.searchText = null
this.filterTargetNames = []
@@ -512,4 +563,19 @@ export default defineComponent({
margin: 0;
padding: 0;
}
+
+/* Placing custom content to the right of a TabView's tabs */
+
+.mavedb-search-tab-view-container {
+ position: relative;
+}
+
+.mavedb-search-tab-view-container .mavedb-search-tab-view-tabs-right {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: 10px 0;
+ padding: 0.75rem 1.25rem;
+ line-height: 1;
+}
diff --git a/src/lib/mavemd.ts b/src/lib/mavemd.ts
index 0d1bc504..3ffacb33 100644
--- a/src/lib/mavemd.ts
+++ b/src/lib/mavemd.ts
@@ -67,3 +67,18 @@ export const MAVE_MD_SCORE_SETS = [
{gene: 'TPK1', urn: 'urn:mavedb:00000001-d-1'},
{gene: 'VHL', urn: 'urn:mavedb:00000675-a-1'}
]
+
+/**
+ * Regular expression for valid CA or PA ids that can be used in ClinGen searches.
+ */
+export const clinGenAlleleIdRegex = /^(CA|PA)[0-9]+$/mi
+
+/**
+ * Regular expression for valid ClinVar Variation IDs that can be used in ClinGen searches.
+ */
+export const clinVarVariationIdRegex = /^[0-9]+$/m
+
+/**
+ * Regular expression for valid Reference SNP cluster IDs that can be used in ClinGen searches.
+ */
+export const rsIdRegex = /^rs[0-9]+$/mi
diff --git a/src/lib/search.ts b/src/lib/search.ts
index 0a8c5497..19f9cccc 100644
--- a/src/lib/search.ts
+++ b/src/lib/search.ts
@@ -1,5 +1,6 @@
import router from '@/router'
import {hgvsSearchStringRegex} from './mave-hgvs'
+import {clinGenAlleleIdRegex, rsIdRegex} from './mavemd'
export function routeToVariantSearchIfVariantIsSearchable(searchText: string | null | undefined): boolean {
if (!searchText || searchText.trim() === '') {
@@ -7,6 +8,18 @@ export function routeToVariantSearchIfVariantIsSearchable(searchText: string | n
}
searchText = searchText.trim()
+ if (clinGenAlleleIdRegex.test(searchText)) {
+ console.log(`Routing to mavemd with ClinGen Allele ID: ${searchText}`)
+ router.push({name: 'mavemd', query: {search:searchText, searchType: 'clinGenAlleleId'}})
+ return true
+ }
+
+ if (rsIdRegex.test(searchText)) {
+ console.log(`Routing to mavemd with RS ID: ${searchText}`)
+ router.push({name: 'mavemd', query: {search: searchText, searchType: 'dbSnpRsId'}})
+ return true
+ }
+
const hgvsMatches = hgvsSearchStringRegex.exec(searchText)
if (hgvsMatches && hgvsMatches.groups) {
const identifier = hgvsMatches.groups.identifier
@@ -17,7 +30,7 @@ export function routeToVariantSearchIfVariantIsSearchable(searchText: string | n
if (accessionRegex.test(identifier)) {
// Transcript: treat as normal HGVS
console.log(`Routing to mavemd with HGVS: ${hgvsMatches[0]}`)
- router.push({name: 'mavemd', query: {search: hgvsMatches[0]}})
+ router.push({name: 'mavemd', query: {search: hgvsMatches[0], searchType: 'hgvs'}})
return true
} else {
// Assume identifier is an HGNC gene symbol, parse description for fuzzy search
@@ -49,7 +62,7 @@ export function routeToVariantSearchIfVariantIsSearchable(searchText: string | n
variantType,
variantPosition,
refAllele,
- altAllele
+ altAllele,
}
})
return true
@@ -57,7 +70,7 @@ export function routeToVariantSearchIfVariantIsSearchable(searchText: string | n
// The search looks like an HGVS string but with an invalid accession, and it's not a p. or c. string preceded by
// a gene name. Forward to the variant search screen, which will deal with the problem.
console.log(`Routing to mavemd with HGVS: ${hgvsMatches[0]}`)
- router.push({name: 'mavemd', query: {search: hgvsMatches[0]}})
+ router.push({name: 'mavemd', query: {search: hgvsMatches[0], searchType: 'hgvs'}})
return true
}
}