BraVue

Colin Gross

2022-01-25

Motivation

  • Single project (no sub-projects)
  • Grep-able codebase
  • Single templating language
  • Single programing language
  • Consolidate styling
  • Testable

Design

  • Client side
  • Vue framework
  • Multipage Application

Architecture

Previous Arch

Multipage Application

bravue/src/pages/
├── gene
│   ├── gene.html
│   ├── GenePage.vue
│   └── main.js
├── home
│   ├── home.html
│   ├── Home.vue
│   └── main.js
├── region
│   ├── region.html
│   ├── RegionPage.vue
│   └── main.js
└── variant
    ├── variant.html
    ├── Variant.vue
    └── main.js

vue.config.js

module.exports = {
  publicPath: '/',
  pages: {
    home: {
      entry:    'src/pages/home/main.js',
      template: 'src/pages/home/home.html',
      filename: 'index.html',
      title:    'Bravo:Home',
      chunks:   ['chunk-vendors', 'chunk-common', 'home']
    variant: {
      entry:    'src/pages/variant/main.js',
      template: 'src/pages/variant/variant.html',
      filename: 'variant.html',
      title:    'Bravo:Variant',
      chunks:   ['chunk-vendors', 'chunk-common', 'variant']

Shared Components

  • Histogram component on gene and region pages.
  • Mostly identical code.
  • Differ on parameters and API endpoint.

Base Component

// BaseSnvCount methods
  methods: {
    load: function(width) {
      axios.post(this.url, { ...}
      ...
    },
    format_y_ticks: function(value) {...},
    initializeSVG:  function() {...},
    draw:           function() {...},
    drawHistogram:  function() {...},
    drawVariants:   function() {...},
    clearDrawing:   function() {...},
  },

Gene Variants Histogram

  • Extends BaseSnvCount component
  • Declares required ensembleId
  • Defines api endpoint
<script>
import BaseSnvCount from '@/components/histogram/BaseSnvCount.vue'

export default {
  name: "GeneSnvCount",
  extends: BaseSnvCount,
  inject: {
    ensemblId: {default: '' }
  },
  computed: {
    url() { return `${this.api}/variants/gene/snv/${this.ensemblId}/histogram` }
  }
}
</script>

Region Variants Histogram

  • Also extends BaseSnvCount component
  • Declares required position data
  • Defines api endpoint
<script>
import BaseSnvCount from '@/components/histogram/BaseSnvCount.vue'

export default {
  name: "RegionSnvCount",
  extends: BaseSnvCount,
  inject: {
    chrom: {default: '11'},
    start: {default: 200000},
    stop:  {default: 201000},
  },
  computed: {
    url() { return `${this.api}/variants/region/snv/${this.chrom}-${this.start}-${this.stop}/histogram` }
  }
}
</script>

Clear Components

<GeneSnvCount 
  :segmentBounds="segmentBounds" 
  :segmentRegions="segmentRegions"
  :filters="filterArray" 
  :visibleVariants="visibleVariants"/>
<RegionSnvCount
  :segmentBounds="segmentBounds" 
  :segmentRegions="segmentRegions"
  :filters="filterArray" 
  :visibleVariants="visibleVariants"/>

Composition

  • Wrap functionality into discrete components
  • Easier to compose different dashboards

Gene Dashboard

<template>
    <GeneInfo :geneData="geneData"/>

    <ToggleList list-title="Panels" list-group="showPanels"
      :list-vars="showPanels"/>
    <ToggleList list-title="Columns" list-group="showCols" 
      :list-vars="showCols"/>

    <GeneSummary :filterArray='filterArray' />

    <SeqDepth 
      :hoveredVariant="hoveredVariant" :segmentBounds="segmentBounds" 
      :segmentRegions="segmentRegions" :givenWidth="childWidth"
      :givenMargins="childMargins"/>

    <TranscriptBars  :geneData="geneData"
      :hoveredVariant="hoveredVariant" :segmentBounds="segmentBounds" 
      :segmentRegions="segmentRegions" :givenWidth="childWidth" 
      :givenMargins="childMargins" />

    <GeneSnvCount  
      :segmentBounds="segmentBounds" 
      :segmentRegions="segmentRegions" :givenWidth="childWidth" 
      :givenMargins="childMargins"
      :filters="filterArray" :visibleVariants="visibleVariants"/>

    <BpCoordBar :segmentBounds="segmentBounds" 
      :segmentRegions="segmentRegions" 
      :givenWidth="childWidth" :givenMargins="childMargins" />

    <FilterBar @filterChange='handleFilterChange'/>

    <GeneSNVTable :filters="filterArray" :doDownload="doDownload"/>
</template>

Tests

  • Vue supports unit and component tests.
  • Handy one liner to run tests when files change:
fd '\.(js|Vue|vue)$' . | entr -c npm run test:unit

Test Component Functions Directly

//describe('SearchBox.vue query parsing.
  it('tickets a dbSNP reference query with endpoint: variant', () => {
    const rsid_result = wrapper.vm.queryToResultTicket('rs12345678')
    expect(rsid_result.endpoint).to.equal('variant')
  })

  it('tickets a range query with endpoint: region', () => {
    const range_result = wrapper.vm.queryToResultTicket('chr11:10,500,100-10,503,000')
    expect(range_result.endpoint).to.equal('region')
  })

  it('defaults to ticket query with endpoint: gene', () => {
    const range_result = wrapper.vm.queryToResultTicket('HBBP1')
    expect(range_result.endpoint).to.equal('gene')
  })

Test Component UI Behavior

describe('QualityFilterButton dropdown', () => {
  it('toggles showDropDown when button is clicked', () => {
    const wrapper = shallowMount(QualityFilterButton, {})

    const initialState = wrapper.vm.showDropDown

    wrapper.find('button').trigger('click')
    expect(wrapper.vm.showDropDown).to.equal(!initialState)

    wrapper.find('button').trigger('click')
    expect(wrapper.vm.showDropDown).to.equal(initialState)
  })
})

Drawbacks

  • Upgrade entire toolchain for all components.
  • UI not locked behind auth.
  • Loss* of parsing params from path
    • Can be reworked if required

Future Work

  • eQTL display:
    • specialized region view
    • region view with different input
  • Separation of concerns:
    • get data
    • display data
  • Single Page App investigation:
    • persist data to avoid reload on “back”
    • load times
    • dist size