Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon committed Sep 4, 2023
1 parent 0649cfe commit 8074895
Show file tree
Hide file tree
Showing 28 changed files with 2,809 additions and 79 deletions.
27 changes: 27 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# For more configuration details:
# https://docs.codecov.io/docs/codecov-yaml

# Check if this file is valid by running in bash:
# curl -X POST --data-binary @.codecov.yml https://codecov.io/validate

# Coverage configuration
# ----------------------
coverage:
status:
patch: false

range: 70..90 # First number represents red, and second represents green
# (default is 70..100)
round: down # up, down, or nearest
precision: 2 # Number of decimal places, between 0 and 5

# Ignoring Paths
# --------------
# which folders/files to ignore

# Pull request comments:
# ----------------------
# Diff is the Coverage Diff of the pull request.
# Files are the files impacted by the pull request
comment:
layout: diff, files # accepted in any order: reach, diff, flags, and/or files
12 changes: 6 additions & 6 deletions frontend/src/api/__tests__/common.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { describe, it, expect } from 'vitest'

import { API_BASE_PREFIX, API_PROXY_BASE_PREFIX } from '../common'
import { API_BASE_PREFIX_ANNONARS, API_BASE_PREFIX_MEHARI } from '../common'

describe('constants', () => {
it('returns the correct proxy API base prefix in production mode', () => {
describe('API_BASE_PREFIX constants', () => {
it('returns the correct API base prefix for annonars in production mode', () => {
const originalMode = import.meta.env.MODE
expect(API_BASE_PREFIX).toBe('/')
expect(API_BASE_PREFIX_ANNONARS).toBe('/proxy/annonars')
import.meta.env.MODE = originalMode
})

it('returns the correct proxy API base prefix in production mode', () => {
it('returns the correct API base prefix for mehari in production mode', () => {
const originalMode = import.meta.env.MODE
expect(API_PROXY_BASE_PREFIX).toBe('/proxy/')
expect(API_BASE_PREFIX_MEHARI).toBe('/proxy/mehari')
import.meta.env.MODE = originalMode
})
})
19 changes: 18 additions & 1 deletion frontend/src/api/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest'

import { roundIt } from '../utils'
import { roundIt, search } from '../utils'

describe('roundIt method', () => {
it('should round a positive value with default digits', () => {
Expand Down Expand Up @@ -33,3 +33,20 @@ describe('roundIt method', () => {
expect(result).toBe('<abbr title="-10.12345">-10.12</abbr>')
})
})

describe('search method', () => {
it('should return route location if match', () => {
const result = search('BRCA1')
expect(result).toEqual({
name: 'gene',
params: {
searchTerm: 'BRCA1'
}
})
})

it.skip('should return null if no match', () => {
const result = search('foo')
expect(result).toBe(null)
})
})
31 changes: 28 additions & 3 deletions frontend/src/api/annonars.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
import { API_PROXY_BASE_PREFIX } from '@/api/common'
import { API_BASE_PREFIX_ANNONARS } from '@/api/common'

const API_BASE_URL = `${API_BASE_PREFIX_ANNONARS}/`

export class AnnonarsClient {
private apiBaseUrl: string
private csrfToken: string | null

constructor(apiBaseUrl?: string, csrfToken?: string) {
this.apiBaseUrl = apiBaseUrl ?? `${API_PROXY_BASE_PREFIX}annonars`
this.apiBaseUrl = apiBaseUrl ?? API_BASE_URL
this.csrfToken = csrfToken ?? null
}

async fetchGeneInfo(hgncId: string): Promise<any> {
const response = await fetch(`${this.apiBaseUrl}/genes/info?hgnc_id=${hgncId}`, {
const response = await fetch(`${this.apiBaseUrl}genes/info?hgnc_id=${hgncId}`, {
method: 'GET'
})
return await response.json()
}

async fetchVariantInfo(
genomeRelease: string,
chromosome: string,
pos: number,
reference: string,
alternative: string
): Promise<any> {
let chrom = chromosome.replace('chr', '')
if (genomeRelease !== 'grch37') {
chrom = `chr${chrom}`
}

const url =
`${this.apiBaseUrl}annos/variant?genome_release=${genomeRelease}&` +
`chromosome=${chrom}&pos=${pos}&reference=${reference}&` +
`alternative=${alternative}`

const response = await fetch(url, {
method: 'GET'
})
return await response.json()
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/api/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const API_BASE_PREFIX = import.meta.env.MODE == 'development' ? '//localhost:8080/' : '/'
export const API_BASE_PREFIX_ANNONARS =
import.meta.env.MODE == 'development' ? '//localhost:8080/proxy/annonars' : '/proxy/annonars'

export const API_PROXY_BASE_PREFIX = `${API_BASE_PREFIX}proxy/`
export const API_BASE_PREFIX_MEHARI =
import.meta.env.MODE == 'development' ? '//localhost:8080/proxy/mehari' : '/proxy/mehari'
33 changes: 33 additions & 0 deletions frontend/src/api/mehari.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { API_BASE_PREFIX_MEHARI } from '@/api/common'

const API_BASE_URL = `${API_BASE_PREFIX_MEHARI}/`

export class MehariClient {
private apiBaseUrl: string
private csrfToken: string | null

constructor(apiBaseUrl?: string, csrfToken?: string) {
this.apiBaseUrl = apiBaseUrl ?? API_BASE_URL
this.csrfToken = csrfToken ?? null
}

async retrieveTxCsq(
genomeRelease: string,
chromosome: string,
pos: number,
reference: string,
alternative: string,
hgnc_id?: string
): Promise<any> {
const hgncSuffix = hgnc_id ? `&hgnc-id=${hgnc_id}` : ''
const url =
`${this.apiBaseUrl}tx/csq?genome-release=${genomeRelease}&` +
`chromosome=${chromosome}&position=${pos}&reference=${reference}&` +
`alternative=${alternative}${hgncSuffix}`

const response = await fetch(url, {
method: 'GET'
})
return await response.json()
}
}
4 changes: 2 additions & 2 deletions frontend/src/api/misc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { API_BASE_PREFIX } from '@/api/common'
import { API_BASE_PREFIX_ANNONARS } from '@/api/common'

const API_BASE_URL = API_BASE_PREFIX
const API_BASE_URL = API_BASE_PREFIX_ANNONARS

export class MiscClient {
private apiBaseUrl: string
Expand Down
128 changes: 128 additions & 0 deletions frontend/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,131 @@ export const roundIt = (value: number, digits: number = 2, label?: string): stri
const useLabel = label ? `${label}: ` : ''
return `<abbr title="${useLabel}${value}">${roundedValue}</abbr>`
}

/**
* Converts a number to a string with thousands separator.
*
* @param value The number to separate.
* @param separator The separator to use.
*/
export const separateIt = (value: number, separator: string = ' '): string => {
const asString = `${value}`
if (!asString.length) {
return '0'
}
const splitString = asString.split('.', 1)
const cardinal = splitString[0]
if (!cardinal?.length) {
splitString[0] = '0'
} else {
const offset = cardinal.length % 3
const arr = [cardinal.slice(0, offset)]
for (let i = 0; i <= cardinal.length; i += 3) {
arr.push(cardinal.slice(offset + i, offset + i + 3))
}
splitString[0] = arr.join(separator)
}
return splitString.join('.')
}

/**
* Returns whether the given variant looks mitochondrial.
*
* @param smallVar Small variant to check.
* @returns whether the position is on the mitochondrial genome
*/
export const isVariantMt = (smallVar: any): boolean => {
return ['MT', 'M', 'chrMT', 'chrM'].includes(smallVar?.chromosome)
}

/**
* Returns whether the given position is in a homopolymer on the mitochondrial chromosome.
*
* @param smallVar Small variant to check.
* @returns whether the position is in a mitochondrial homopolymer
*/
export const isVariantMtHomopolymer = (smallVar: any): any => {
if (!smallVar) {
return false
}
const { start, end } = smallVar
const positionCheck = (pos: number) => {
return (
(pos >= 66 && pos <= 71) ||
(pos >= 300 && pos <= 316) ||
(pos >= 513 && pos <= 525) ||
(pos >= 3106 && pos <= 3107) ||
(pos >= 12418 && pos <= 12425) ||
(pos >= 16182 && pos <= 16194)
)
}
if (isVariantMt(smallVar)) {
return positionCheck(start) || positionCheck(end)
}
}

/**
* Take a `searchTerm` and return a route location that can be used to navigate to
* the correct page.
*
* @param searchTerm The search term to use.
*/
export const search = (searchTerm: string, genomeRelease: string) => {
interface RouteLocationFragment {
name: string
params?: any
}

type RouteLoctionBuilder = () => RouteLocationFragment

// We iterate the regexps in the `Map` and will use the route from the
// first match.
const SEARCH_REGEXPS: [RegExp, RouteLoctionBuilder][] = [
[
/^chr\d+:\d+:[A-Z]:[A-Z]$/,
(): RouteLocationFragment => ({
name: 'variant',
params: {
searchTerm: searchTerm,
genomeRelease: genomeRelease
}
})
],
[
/^.*$/,
(): RouteLocationFragment => ({
name: 'gene',
params: {
searchTerm: searchTerm,
genomeRelease: genomeRelease
}
})
]
]

for (const [regexp, getRoute] of SEARCH_REGEXPS) {
if (regexp.test(searchTerm)) {
const routeLocation = getRoute()
console.log(`term ${searchTerm} matched ${regexp}, route is`, routeLocation)
return routeLocation
}
}
return null
}

/**
* Take a query string and return an object with the chromosome, pos, reference and
* alternative value.
*
* @param query Incoming query string
*/
export const infoFromQuery = (query: string): any => {
const [chromosome, pos, reference, alternative, hgnc_id] = query.split(':')
return {
chromosome: chromosome,
pos: pos,
reference: reference,
alternative: alternative,
hgnc_id: hgnc_id
}
}
40 changes: 38 additions & 2 deletions frontend/src/components/HeaderDetailPage.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import SearchBar from '@/components/SearchBar.vue'
import { search } from '@/api/utils'
export interface Props {
searchTerm?: string
genomeRelease?: string
}
const props = withDefaults(defineProps<Props>(), {
searchTerm: '',
genomeRelease: 'grch37'
})
const router = useRouter()
const searchTermRef = ref(props.searchTerm)
const genomeReleaseRef = ref(props.genomeRelease)
const performSearch = async () => {
const routeLocation: any = search(searchTermRef.value, genomeReleaseRef.value)
if (routeLocation) {
router.push(routeLocation)
} else {
console.error(`no route found for ${searchTermRef.value}`)
}
}
</script>

<template>
<v-app-bar app class="top-bar">
<v-toolbar-title>
<router-link to="/">
<img src="@/assets/reev-logo.svg" id="logo" class="nav" alt="logo" width="100" />
<img src="@/assets/reev-logo.svg" id="logo" alt="logo" width="100" />
</router-link>
</v-toolbar-title>
<SearchBar
class="top-search-bar"
v-model:search-term="searchTermRef"
v-model:genome-release="genomeReleaseRef"
@click-search="performSearch"
/>
<v-spacer></v-spacer>
<v-toolbar-items class="topbar-links">
<v-btn id="about" to="/about"> About </v-btn>
Expand All @@ -19,14 +51,18 @@
border-bottom: 2px solid rgb(111, 100, 210);
}
.top-search-bar {
display: flex;
width: 50%;
}
.topbar-links {
display: flex;
margin: 0 10px;
}
#logo {
margin-left: 25px;
margin-right: 10px;
margin-top: 10px;
}
</style>
Loading

0 comments on commit 8074895

Please sign in to comment.