Skip to content

Commit

Permalink
feat: adding SeqvarConsequencesCard (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 30, 2024
1 parent 0578f02 commit d5df980
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/api/mehari/fixture.seqvarCsqResponse.BRCA1.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"result": [
{
"consequences": ["MissenseVariant"],
"feature_tag": ["ManeSelect"],
"putative_impact": "Moderate",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
Expand Down
44 changes: 41 additions & 3 deletions src/api/mehari/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,44 @@ export enum SeqvarConsequence {
UpstreamGeneVariant = 'upstream_gene_variant'
}

/**
* Interface for the rank as returned by API.
*/
export interface Rank$Api {
/** The exon / intron number. */
rank: number
/** The total number of exons / introns. */
total: number
}

/**
* Interface for the rank.
*/
export interface Rank {
/** The exon / intron number. */
rank: number
/** The total number of exons / introns. */
total: number
}

/**
* Helper class for converting `Rank$Api` to `Rank`.
*/
class Rank$Type {
/** Convert `Rank$Api` to `Rank`. */
fromJson(json: Rank$Api): Rank {
return {
rank: json.rank,
total: json.total
}
}
}

/**
* Helper for converting `Rank$Api` to `Rank`.
*/
export const Rank: Rank$Type = new Rank$Type()

/**
* Interface for one entry in the result as returned by API.
*/
Expand All @@ -93,7 +131,7 @@ export interface SeqvarResultEntry$Api {
/** The feature tags. */
feature_tag: string[]
/** The exon / intron rank. */
rank?: number
rank?: Rank$Api
/** HGVS c. notation. */
hgvs_t?: string
/** HGVS p. notation. */
Expand Down Expand Up @@ -131,7 +169,7 @@ export interface SeqvarResultEntry {
/** The feature tags. */
featureTag: string[]
/** The exon / intron rank. */
rank?: number
rank?: Rank
/** HGVS c. notation. */
hgvsT?: string
/** HGVS p. notation. */
Expand Down Expand Up @@ -163,7 +201,7 @@ class SeqvarResultEntry$Type {
featureId: json.feature_id,
featureBiotype: json.feature_biotype,
featureTag: json.feature_tag,
rank: json.rank,
rank: json.rank === undefined ? undefined : Rank.fromJson(json.rank),
hgvsT: json.hgvs_t,
hgvsP: json.hgvs_p,
txPos: json.tx_pos,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import fs from 'fs'
import path from 'path'
import { describe, expect, it } from 'vitest'

import { SeqvarResult } from '../../api/mehari/types'
import { setupMountedComponents } from '../../lib/testUtils'
import SeqvarConsequencesCard from './SeqvarConsequencesCard.vue'

/** Fixtures */
const seqvarCsqResult = SeqvarResult.fromJson(
JSON.parse(
fs.readFileSync(
path.resolve(__dirname, '../../api/mehari/fixture.seqvarCsqResponse.BRCA1.json'),
'utf-8'
)
)
)

describe.concurrent('SeqvarConsequencesCard.vue', async () => {
it('renders the consequence info', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: SeqvarConsequencesCard },
{
props: {
consequences: seqvarCsqResult.result
}
}
)

// act: nothing, only test rendering

// assert:
const table = wrapper.find('table')
expect(table.exists()).toBe(true)
const headers = table.findAll('th')
expect(headers.length).toBe(6)
expect(headers[0].text()).toBe('Gene')
expect(headers[1].text()).toBe('Transcript')
expect(headers[2].text()).toBe('Consequence')
expect(headers[3].text()).toBe('HGVS.p')
expect(headers[4].text()).toBe('HGVS.t')
expect(headers[5].text()).toBe('Exon/Intron')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { JsonValue } from '@protobuf-ts/runtime'
import type { Meta, StoryObj } from '@storybook/vue3'

import seqvarCsqResultBrca1Json from '../../api/mehari/fixture.seqvarCsqResponse.BRCA1.json'
import { SeqvarResult } from '../../api/mehari/types'
import SeqvarConsequencesCard from './SeqvarConsequencesCard.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 seqvarCsqResponseBrca1 = SeqvarResult.fromJson(seqvarCsqResultBrca1Json as JsonValue)

const meta = {
title: 'Seqvar/SeqvarConsequencesCard',
component: SeqvarConsequencesCard,
tags: ['autodocs'],
argTypes: {
consequences: { control: { type: 'object' } }
},
args: { consequences: seqvarCsqResponseBrca1.result }
} satisfies Meta<typeof SeqvarConsequencesCard>

export default meta
type Story = StoryObj<typeof meta>

export const BRCA1: Story = {
args: {
consequences: seqvarCsqResponseBrca1.result
}
}
85 changes: 85 additions & 0 deletions src/components/SeqvarConsequencesCard/SeqvarConsequencesCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { SeqvarResultEntry } from '../../api/mehari/types'
import DocsLink from '../DocsLink/DocsLink.vue'
/** This component's */
const props = withDefaults(

Check warning on line 6 in src/components/SeqvarConsequencesCard/SeqvarConsequencesCard.vue

View workflow job for this annotation

GitHub Actions / Lint

'props' is assigned a value but never used. Allowed unused vars must match /^_/u
defineProps<{
/** The variant consequences. */
consequences?: SeqvarResultEntry[]
}>(),
{
consequences: () => []
}
)
</script>

<template>
<template v-if="!consequences?.length">
<!-- no ENSG => display loader -->
<v-skeleton-loader class="mt-3 mx-auto border" type="table" />
</template>
<template v-else>
<v-card>
<v-card-title class="pb-0 pr-2">
Consequences
<DocsLink anchor="consequences" />
</v-card-title>
<v-card-subtitle class="text-overline">
Variant Consequences on Overlapping Transcripts
</v-card-subtitle>
<v-card-text>
<v-table>
<thead>
<tr>
<th>Gene</th>
<th>Transcript</th>
<th>Consequence</th>
<th>HGVS.p</th>
<th>HGVS.t</th>
<th>Exon/Intron</th>
</tr>
</thead>
<tbody>
<template v-if="consequences?.length">
<tr v-for="(oneTxCsq, idx) in consequences" :key="idx">
<td>{{ oneTxCsq.geneSymbol }}</td>
<td>
{{ oneTxCsq.featureId }}
<small> ({{ oneTxCsq.featureBiotype }}) </small>
<v-chip
v-if="(oneTxCsq.featureTag ?? []).includes('ManeSelect')"
color="primary"
rounded="xl"
class="ml-3"
>
MANE Select
</v-chip>
<v-chip
v-if="(oneTxCsq.featureTag ?? []).includes('ManePlusClinical')"
color="primary"
rounded="xl"
class="ml-3"
>
MANE Plus Clinical
</v-chip>
</td>
<td>{{ oneTxCsq.consequences.join(', ') }}</td>
<td>{{ oneTxCsq.hgvsT }}</td>
<td>{{ oneTxCsq.hgvsP }}</td>
<td>{{ oneTxCsq.rank!.rank }} / {{ oneTxCsq.rank!.total }}</td>
</tr>
</template>
<template v-else>
<tr>
<td colspan="6" class="text-center font-italic text-grey-darken-2">
No overlapping transcripts
</td>
</tr>
</template>
</tbody>
</v-table>
</v-card-text>
</v-card>
</template>
</template>

0 comments on commit d5df980

Please sign in to comment.