Skip to content

Commit

Permalink
feat: adding SeqvarToolsCard (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 31, 2024
1 parent 2426ad1 commit 742b84d
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 0 deletions.
90 changes: 90 additions & 0 deletions src/components/SeqvarToolsCard/SeqvarToolsCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import fs from 'fs'
import path from 'path'
import { describe, expect, it } from 'vitest'

import { SeqvarInfoResponse } from '../../api/annonars/types'
import type { Seqvar } from '../../lib/genomicVars'
import { setupMountedComponents } from '../../lib/testUtils'
import SeqvarToolsCard from './SeqvarToolsCard.vue'

/** Fixtures */
const seqvarInfoResponseBrca1 = SeqvarInfoResponse.fromJson(
JSON.parse(
fs.readFileSync(
path.resolve(__dirname, '../../api/annonars/fixture.variantInfo.BRCA1.json'),
'utf-8'
)
)
)

// Sequence Variant in BRCA1
const seqvarBrca1: Seqvar = {
genomeBuild: 'grch37',
chrom: '17',
pos: 41215920,
del: 'G',
ins: 'T',
userRepr: 'grch37-17-41215920-G-T'
}

describe.concurrent('SeqvarToolsCard', async () => {
it('renders the card', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: SeqvarToolsCard },
{
props: {
seqvar: seqvarBrca1,
varAnnos: seqvarInfoResponseBrca1.result
}
}
)

// act: nothing, only test rendering

// assert:
expect(wrapper.text()).toContain('Genome Browsers')
expect(wrapper.text()).toContain('Other Resources')
expect(wrapper.text()).toContain('Local IGV')
const launchIcons = wrapper.findAll('.mdi-launch')
expect(launchIcons.length).toBe(9)
})

it('renders skeleton loader with undefined seqvar', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: SeqvarToolsCard },
{
props: {
seqvar: undefined,
varAnnos: seqvarInfoResponseBrca1.result
}
}
)

// act: nothing, only test rendering

// assert:
const loader = wrapper.findComponent({ name: 'VSkeletonLoader' })
expect(loader.exists()).toBe(true)
})

it('renders skeleton loader undefined varAnnos', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: SeqvarToolsCard },
{
props: {
seqvar: seqvarInfoResponseBrca1,
varAnnos: undefined
}
}
)

// act: nothing, only test rendering

// assert:VSkeletonLoader
const loader = wrapper.findComponent({ name: 'VSkeletonLoader' })
expect(loader.exists()).toBe(true)
})
})
35 changes: 35 additions & 0 deletions src/components/SeqvarToolsCard/SeqvarToolsCard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { JsonValue } from '@protobuf-ts/runtime'
import type { Meta, StoryObj } from '@storybook/vue3'

import fixtureSeqvarInfoBrca1Json from '../../api/annonars/fixture.variantInfo.BRCA1.json'
import { SeqvarInfoResponse, SeqvarInfoResult } from '../../api/annonars/types'
import { SeqvarImpl } from '../../lib/genomicVars'
import SeqvarToolsCard from './SeqvarToolsCard.vue'

// Here, fixture data is loaded via `import` from JSON file. Reading the file
// as in the tests fails with "process is not defined" error in the browser.

// @ts-ignore
const seqvarInfoResponseBrca1 = SeqvarInfoResponse.fromJson(fixtureSeqvarInfoBrca1Json as JsonValue)

const seqvarBrca1 = new SeqvarImpl('grch37', '18', 41215920, 'G', 'T')

const meta = {
title: 'Seqvar/SeqvarToolsCard',
component: SeqvarToolsCard,
tags: ['autodocs'],
argTypes: {
varAnnos: SeqvarInfoResult
},
args: { varAnnos: seqvarInfoResponseBrca1.result }
} satisfies Meta<typeof SeqvarToolsCard>

export default meta
type Story = StoryObj<typeof meta>

export const BRCA1: Story = {
args: {
seqvar: seqvarBrca1,
varAnnos: seqvarInfoResponseBrca1.result
}
}
207 changes: 207 additions & 0 deletions src/components/SeqvarToolsCard/SeqvarToolsCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<script setup lang="ts">
import { computed } from 'vue'
import { SeqvarInfoResult } from '../../api/annonars/types'
import { type Seqvar } from '../../lib/genomicVars'
import DocsLink from '../DocsLink/DocsLink.vue'
/** This component's props. */
const props = defineProps<{
/** Annotated sequence variant. */
seqvar?: Seqvar
/** Annotations. */
varAnnos?: SeqvarInfoResult
}>()
const ucscLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const db = props.seqvar.genomeBuild === 'grch37' ? 'hg19' : 'hg38'
return (
`https://genome-euro.ucsc.edu/cgi-bin/hgTracks?db=${db}&position=` +
`${props.seqvar.chrom}:${props.seqvar.pos}-` +
`${props.seqvar.pos + props.seqvar.del.length}`
)
})
const ensemblLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const loc = `${props.seqvar.chrom}:${props.seqvar.pos}-${
props.seqvar.pos + props.seqvar.del.length
}`
if (props.seqvar.genomeBuild === 'grch37') {
return `https://grch37.ensembl.org/Homo_sapiens/Location/View?r=${loc}`
} else if (props.seqvar.genomeBuild === 'grch38') {
return `https://ensembl.org/Homo_sapiens/Location/View?r=${loc}`
}
return '#'
})
const dgvLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const db = props.seqvar.genomeBuild === 'grch37' ? 'hg19' : 'hg38'
return (
`http://dgv.tcag.ca/gb2/gbrowse/dgv2_${db}/?name=${props.seqvar.chrom}:` +
`${props.seqvar.pos}-${props.seqvar.pos + props.seqvar.del.length};search=Search`
)
})
const gnomadLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const dataset = props.seqvar.genomeBuild === 'grch37' ? 'gnomad_r2_1' : 'gnomad_r3'
return (
`https://gnomad.broadinstitute.org/region/${props.seqvar.chrom}:` +
`${props.seqvar.pos}-${props.seqvar.pos + props.seqvar.del.length}?dataset=${dataset}`
)
})
const mt85Linkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
if (props.seqvar.genomeBuild === 'grch37') {
return (
`https://www.genecascade.org/MT85/ChrPos85.cgi?chromosome=${props.seqvar.chrom}` +
`&position=${props.seqvar.pos}&ref=${props.seqvar.del}&alt=${props.seqvar.ins}`
)
} else {
return ''
}
})
const mt2021Linkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
if (props.seqvar.genomeBuild === 'grch37') {
return (
`https://www.genecascade.org/MTc2021/ChrPos102.cgi?chromosome=${props.seqvar.chrom}` +
`&position=${props.seqvar.pos}&ref=${props.seqvar.del}&alt=${props.seqvar.ins}`
)
} else {
return ''
}
})
const varsomeLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const urlRelease = props.seqvar.genomeBuild === 'grch37' ? 'hg19' : 'hg38'
const chrom = props.seqvar.chrom.startsWith('chr')
? props.seqvar.chrom
: `chr${props.seqvar.chrom}`
return (
`https://varsome.com/variant/${urlRelease}/${chrom}:${props.seqvar.pos}:` +
`${props.seqvar.del}:${props.seqvar.ins}`
)
})
const franklinLinkout = computed<string>(() => {
if (!props.seqvar) {
return '#'
}
const { chrom, pos, del, ins } = props.seqvar
const urlRelease = props.seqvar.genomeBuild === 'grch37' ? 'hg19' : 'hg38'
return `https://franklin.genoox.com/clinical-db/variant/snp/chr${chrom.replace(
'chr',
''
)}-${pos}-${del}-${ins}-${urlRelease}`
})
const jumpToLocus = async () => {
const chrPrefixed = props.seqvar?.chrom.startsWith('chr')
? props.seqvar?.chrom
: `chr${props.seqvar?.chrom}`
// NB: we allow the call to fetch here as it goes to local IGV.
await fetch(
`http://127.0.0.1:60151/goto?locus=${chrPrefixed}:${props.seqvar?.pos}-${
(props.seqvar?.pos ?? 0) + (props.seqvar?.del?.length ?? 0)
}`
).catch((e) => {
const msg = "Couldn't connect to IGV. Please make sure IGV is running and try again."
alert(msg)
console.error(msg, e)
})
}
interface Linkout {
href: string
label: string
}
const genomeBrowsers = computed<Linkout[]>(() => {
return [
{ label: 'ENSEMBL', href: ensemblLinkout.value },
{ label: 'UCSC', href: ucscLinkout.value }
]
})
const resources = computed<Linkout[]>(() => {
return [
{ label: 'DGV', href: dgvLinkout.value },
{ label: 'gnomAD', href: gnomadLinkout.value },
{ label: 'varsome', href: varsomeLinkout.value },
{ label: 'genoox Franklin', href: franklinLinkout.value },
{ label: 'MutationTaster 85', href: mt85Linkout.value },
{ label: 'MutationTaster 2021', href: mt2021Linkout.value }
]
})
</script>

<template>
<template v-if="seqvar === undefined || varAnnos === undefined">
<v-skeleton-loader type="card" />
</template>
<template v-else>
<v-card>
<v-card-title class="pb-0 pr-2">
Variant Tools
<DocsLink anchor="variant-tools" />
</v-card-title>

<v-card-subtitle class="text-overline"> Genome Browsers </v-card-subtitle>
<v-card-text class="pt-3">
<v-btn
v-for="linkout in genomeBrowsers"
:key="linkout.label"
:href="linkout.href"
target="_blank"
prepend-icon="mdi-launch"
variant="tonal"
rounded="sm"
class="mr-2"
>
{{ linkout.label }}
</v-btn>
</v-card-text>

<v-card-subtitle class="text-overline"> Other Resources </v-card-subtitle>
<v-card-text class="pt-3">
<v-btn
v-for="linkout in resources"
:key="linkout.label"
:href="linkout.href"
target="_blank"
prepend-icon="mdi-launch"
variant="tonal"
rounded="sm"
class="mr-2"
>
{{ linkout.label }}
</v-btn>
</v-card-text>

<v-card-actions>
<v-btn prepend-icon="mdi-launch" @click.prevent="jumpToLocus()"> Jump in Local IGV </v-btn>
</v-card-actions>
</v-card>
</template>
</template>

0 comments on commit 742b84d

Please sign in to comment.