Skip to content

Commit

Permalink
feat: adding GeneLiteratureCard (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 29, 2024
1 parent 6df53a2 commit ad041aa
Show file tree
Hide file tree
Showing 14 changed files with 11,828 additions and 2 deletions.
9 changes: 7 additions & 2 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { type Preview, setup } from '@storybook/vue3'
import { createPinia } from 'pinia'
import { type App } from 'vue'

import { registerPlugins } from '../src/plugins'
import { withVuetifyTheme } from './withVuetifyTheme.decorator'

// import type { StoryIdentifier } from "@storybook/types"
export const pinia = createPinia()

const preview: Preview = {
parameters: {
Expand All @@ -27,7 +29,10 @@ const preview: Preview = {
}
}

setup((app) => {
setup((app: App) => {
// Use pinia for state management, also in pinia.
console.log('Using pinia for state management')
app.use(pinia)
// Registers your app's plugins into Storybook
registerPlugins(app)
})
Expand Down
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"dependencies": {
"@mdi/font": "^7.4.47",
"@protobuf-ts/runtime": "^2.9.3",
"@types/luxon": "^3.4.2",
"luxon": "^3.4.4",
"title-case": "^4.3.1",
"vega": "^5.27.0",
"vega-embed": "^6.24.0",
Expand Down
69 changes: 69 additions & 0 deletions src/api/pubtator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { SearchResult } from './types'

export * from './types'

/** API base URL to use. */
const API_BASE_URL = '/internal/proxy/pubtator3-api'

/**
* Client for PubTator V3 API queries.
*/
export class PubtatorClient {
/** API base URL to use in this client instance. */
private apiBaseUrl: string

/**
* Create a new PubTator client.
*
* @param apiBaseUrl The API base URL to use, defaults to `API_BASE_URL`.
*/
constructor(apiBaseUrl?: string) {
this.apiBaseUrl = apiBaseUrl ?? API_BASE_URL
}

/**
* Perform search for the given HGNC symbol.
*
* @param hgncSymbol HGNC symbol to search for.
* @returns Promise for the search results.
* @throws Error if the search fails.
*/
async performSearch(hgncSymbol: string): Promise<{ [key: string]: SearchResult }> {
const url = `${this.apiBaseUrl}/search/?text=@GENE_${hgncSymbol}`
const searchRes = await fetch(url, {
method: 'GET'
})
if (!searchRes.ok) {
throw new Error(`Error running PubTator 3 search: ${searchRes.statusText}`)
}
const searchData = await searchRes.json()

// Then, extract PMID list and retrieve biocjson for the PMIDs
const pmids: string[] = searchData!.results!.map((doc: any) => doc.pmid)
const exportRes = await fetch(
`${this.apiBaseUrl}}/publications/export/biocjson` + `?pmids=${pmids.join(',')}`
)
if (!exportRes.ok) {
throw new Error(`Error running PubTator 3 export: ${exportRes.statusText}`)
}
const exportDataText = await exportRes.text()
const exportDataLines = exportDataText.split(/\n/)

// Zip search results and exports into searchResults
const searchResults: { [key: string]: SearchResult } = {}
for (const searchDataRecord of searchData.results) {
searchResults[searchDataRecord.pmid] = {
searchResult: searchDataRecord,
abstract: undefined
}
}
for (const exportDataLine of exportDataLines) {
if (exportDataLine) {
const exportDataRecord = JSON.parse(exportDataLine)
searchResults[exportDataRecord.pmid].abstract = exportDataRecord
}
}

return searchResults
}
}
7 changes: 7 additions & 0 deletions src/api/pubtator/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Interface for search result entry plus PubMed abstract. */
export interface SearchResult {
/** The search result record. */
searchResult: any
/** The PubMed abstract. */
abstract: any
}
62 changes: 62 additions & 0 deletions src/components/GeneLiteratureCard/GeneLiteratureCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import fs from 'fs'
import path from 'path'
import { describe, expect, test } from 'vitest'

import { setupMountedComponents } from '../../lib/testUtils'
import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base'
import { SearchResults } from '../../store/pubtator'
import { StoreState } from '../../store/types'
import GeneLiteratureCard from './GeneLiteratureCard.vue'

// Load fixture data for gene TGDS (little data) and BRCA1 (lots of data).
const geneInfoTgds = GeneInfoRecord.fromJsonString(
fs.readFileSync(
path.resolve(__dirname, '../GenePathogenicityCard/fixture.geneInfo.TGDS.json'),
'utf8'
)
)
const geneInfoBrca1 = GeneInfoRecord.fromJsonString(
fs.readFileSync(
path.resolve(__dirname, '../GenePathogenicityCard/fixture.geneInfo.BRCA1.json'),
'utf8'
)
)
const searchResultsTgds = JSON.parse(
fs.readFileSync(path.resolve(__dirname, './fixture.pubtatorResults.TGDS.json'), 'utf8')
) as SearchResults
const searchResultsBrca1 = JSON.parse(
fs.readFileSync(path.resolve(__dirname, './fixture.pubtatorResults.BRCA1.json'), 'utf8')
) as SearchResults

describe.concurrent('GeneLiteratureCard.vue', async () => {
test.each([
['BRCA1', geneInfoBrca1, searchResultsBrca1],
['TGDS', geneInfoTgds, searchResultsTgds]
])(
'renders for gene %s',
async (hgncSymbol: string, geneInfo: GeneInfoRecord, searchResults: SearchResults) => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: GeneLiteratureCard },
{
props: {
geneInfo
},
initialStoreState: {
pubtatorStore: {
storeState: StoreState.Active,
hgncSymbol,
searchResults
}
}
}
)

// act: nothing, only test rendering

// assert:
const vCards = wrapper.findAllComponents({ name: 'VCard' })
expect(vCards.length).toBe(1)
}
)
})
65 changes: 65 additions & 0 deletions src/components/GeneLiteratureCard/GeneLiteratureCard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { JsonValue } from '@protobuf-ts/runtime'
import type { Meta, StoryObj } from '@storybook/vue3'

import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base'
import { StoreState } from '../../store'
import { usePubtatorStore } from '../../store/pubtator'
import geneInfoBrca1Json from '../GenePathogenicityCard/fixture.geneInfo.BRCA1.json'
import geneInfoTgdsJson from '../GenePathogenicityCard/fixture.geneInfo.TGDS.json'
import GeneLiteratureCard from './GeneLiteratureCard.vue'
import searchResultsBrca1Json from './fixture.pubtatorResults.BRCA1.json'
import searchResultsTgdsJson from './fixture.pubtatorResults.TGDS.json'

// 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 geneInfoTgds = GeneInfoRecord.fromJson(geneInfoTgdsJson as JsonValue)
// @ts-ignore
const geneInfoBrca1 = GeneInfoRecord.fromJson(geneInfoBrca1Json as JsonValue)

const meta = {
title: 'Gene/GeneLiteratureCard',
component: GeneLiteratureCard,
tags: ['autodocs'],
argTypes: {
geneInfo: { control: { type: 'object' } }
},
args: {
geneInfo: geneInfoTgds,
skipLoadViaStore: true
}
} satisfies Meta<typeof GeneLiteratureCard>

export default meta

type Story = StoryObj<typeof meta>

export const TGDS: Story = {
args: {
geneInfo: geneInfoTgds
},
play: async () => {
// Setup the store contents after story selection.
const pubtatorStore = usePubtatorStore()
pubtatorStore.storeState = StoreState.Loading
pubtatorStore.hgncSymbol = 'TGDS'
pubtatorStore.searchResults = searchResultsTgdsJson
pubtatorStore.storeState = StoreState.Active
}
}

export const BRCA1: Story = {
args: {
geneInfo: geneInfoBrca1
},
play: async () => {
console.log('play...')
// Setup the store contents after story selection.
const pubtatorStore = usePubtatorStore()
pubtatorStore.storeState = StoreState.Loading
pubtatorStore.hgncSymbol = 'BRCA1'
pubtatorStore.searchResults = searchResultsBrca1Json
pubtatorStore.storeState = StoreState.Active
}
}
Loading

0 comments on commit ad041aa

Please sign in to comment.