Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: more integrated search component #258

Merged
merged 1 commit into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions frontend/src/components/HeaderDetailPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ const CaseInformationCard = defineAsyncComponent(
() => import('@/components/CaseInformationCard.vue')
)

/** Genome release string values. */
type GenomeRelease = 'grch37' | 'grch38'

export interface Props {
searchTerm?: string
genomeRelease?: string
genomeRelease?: GenomeRelease
}

const props = withDefaults(defineProps<Props>(), {
Expand Down Expand Up @@ -65,9 +68,10 @@ watch(() => props.searchTerm, updateTerms)
v-model:search-term="searchTermRef"
v-model:genome-release="genomeReleaseRef"
class="top-search-bar"
density="compact"
@click-search="performSearch"
/>
<v-spacer />
<v-spacer></v-spacer>
<v-toolbar-items class="topbar-links">
<v-dialog scrollable width="auto" location="top">
<template #activator="{ props: vProps }">
Expand Down
136 changes: 78 additions & 58 deletions frontend/src/components/SearchBar.vue
Original file line number Diff line number Diff line change
@@ -1,78 +1,98 @@
<script setup lang="ts">
export interface GenomeReleaseChoice {
value: string
/** Genome release string values. */
type GenomeRelease = 'grch37' | 'grch38'

/** Type for genome releases. */
interface GenomeReleaseChoice {
value: GenomeRelease
label: string
}

export interface Props {
/** The choices of genomes available. */
const GENOME_RELEASES: GenomeReleaseChoice[] = [
{ value: 'grch37', label: 'GRCh37' },
{ value: 'grch38', label: 'GRCh38' }
]

/** Mapping from gennome release name to `GenomeReleaseChoice`. */
const GENOME_RELEASES_MAP: { [key: string]: GenomeReleaseChoice } = Object.fromEntries(
GENOME_RELEASES.map((choice) => [choice.value, choice])
)

/** Type definition for component's props. */
interface Props {
searchTerm?: string
genomeRelease?: string
genomeReleaseChoices?: GenomeReleaseChoice[]
genomeRelease?: GenomeRelease
density?: 'default' | 'comfortable' | 'compact'
}

/** Define the component's props. */
const props = withDefaults(defineProps<Props>(), {
searchTerm: '',
genomeRelease: 'grch37',
density: 'default'
})

/** Launch search if any term has been entered. */
const runSearch = async () => {
if (props.searchTerm) {
emit('clickSearch', props.searchTerm, props.genomeRelease)
}
}

/** Define the emits. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emit = defineEmits<{
(event: 'update:searchTerm' | 'update:genomeRelease', value: string): void
(event: 'clickSearch', searchTerm: string, genomeRelease: string): void
}>()

const props = withDefaults(defineProps<Props>(), {
searchTerm: '',
genomeRelease: 'grch37',
genomeReleaseChoices: () => [
{ value: 'grch37', label: 'GRCh37' },
{ value: 'grch38', label: 'GRCh38' }
]
})
</script>

<template>
<v-toolbar id="search-bar" floating>
<div class="d-flex d-flex-row">
<v-text-field
id="search-term"
class="my-3 search-term"
:label="props.density != 'compact' ? 'Search for variant or gene' : undefined"
prepend-inner-icon="mdi-magnify"
rounded="xl"
variant="outlined"
hide-details
single-line
:model-value="props.searchTerm"
label="Enter search term"
:density="props.density"
clearable
:hide-details="true"
:model-value="searchTerm"
@input="$emit('update:searchTerm', $event.target.value)"
@keydown.enter="$emit('clickSearch', props.searchTerm, props.genomeRelease)"
/>
<div>
<v-select
id="genome-release"
variant="outlined"
hide-details
single-line
:model-value="props.genomeRelease"
:items="props.genomeReleaseChoices"
item-title="label"
item-value="value"
label="Genome Release"
@update:model-value="$emit('update:genomeRelease', $event)"
/>
</div>

<v-btn
id="search"
color="primary"
@click="$emit('clickSearch', props.searchTerm, props.genomeRelease)"
@keydown.enter="() => runSearch()"
>
<v-icon>mdi-magnify</v-icon>
search
</v-btn>
</v-toolbar>
<template #append-inner>
<v-menu :transition="false">
<template #activator="{ props: innerProps }">
<v-btn
color="black"
v-bind="innerProps"
rounded="xs"
spacing="compact"
append-icon="mdi-chevron-down"
variant="text"
class="genome-release-menu"
>
{{ GENOME_RELEASES_MAP[genomeRelease]?.label }}
</v-btn>
</template>
<v-list>
<v-list-item
v-for="{ value, label } in GENOME_RELEASES"
:key="value"
:value="value"
@click.prevent="() => runSearch()"
>
<v-list-item-title>{{ label }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-btn variant="text" rounded="xl" class="start-search" @click.prevent="() => runSearch()">
<v-icon>mdi-rocket-launch</v-icon>
</v-btn>
</template>
</v-text-field>
</div>
</template>

<style scoped>
#search-bar {
background-color: white;
border: 1px solid #455a64;
border-radius: 10px;
padding: 0 10px;
}

#search {
margin-left: 10px;
}
</style>
29 changes: 11 additions & 18 deletions frontend/src/components/__tests__/SearchBar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ describe.concurrent('SearchBar.vue', () => {
)

const textField = wrapper.find('.v-text-field')
const select = wrapper.find('.v-select')
const searchButton = wrapper.find('#search')
const genomeReleaseMenu = wrapper.find('.genome-release-menu')
const searchButton = wrapper.find('.start-search')
expect(textField.exists()).toBe(true)
expect(select.exists()).toBe(true)
expect(genomeReleaseMenu.exists()).toBe(true)
expect(searchButton.exists()).toBe(true)
expect(textField.html()).toMatch('Enter search term')
expect(select.html()).toMatch('Genome Release')
expect(select.html()).toMatch('label')
expect(select.html()).toMatch('value')
expect(select.html()).toMatch('GRCh37')
expect(textField.html()).toMatch('Search for variant or gene')
expect(genomeReleaseMenu.html()).toMatch('GRCh37')
expect(searchButton.html()).toMatch('search')
})

Expand All @@ -47,22 +44,18 @@ describe.concurrent('SearchBar.vue', () => {
}
)

const textField = wrapper.find('#search-term') as any
const textField = wrapper.find('.search-term input') as any
expect(textField.exists()).toBe(true)
await textField.setValue('test')
expect(textField.element.value).toBe('test')

const select = wrapper.find('#genome-release') as any
const select = wrapper.find('.genome-release-menu') as any
expect(select.exists()).toBe(true)

const searchButton = wrapper.findComponent('#search') as any
expect(searchButton.exists()).toBe(true)
await searchButton.trigger('click')
await nextTick()
await searchButton.trigger('click')
const genomeReleaseMenu = wrapper.findComponent('.genome-release-menu') as any
expect(genomeReleaseMenu.exists()).toBe(true)
await genomeReleaseMenu.trigger('click')
await nextTick()
expect(wrapper.emitted()).toHaveProperty('click')
expect(wrapper.emitted('click')).toHaveLength(2)
})

it('correctly emits search', async () => {
Expand All @@ -86,7 +79,7 @@ describe.concurrent('SearchBar.vue', () => {
await searchBar.setValue('grch37', 'genomeRelease')
expect(searchBar.emitted()).toHaveProperty('update:searchTerm')
expect(searchBar.emitted()).toHaveProperty('update:genomeRelease')
const searchButton = searchBar.findComponent('#search') as any
const searchButton = searchBar.findComponent('button.start-search') as any
expect(searchButton.exists()).toBe(true)
await searchButton.trigger('click')
await nextTick()
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/views/GenesListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { StoreState } from '@/stores/misc'
// Components
const HeaderDetailPage = defineAsyncComponent(() => import('@/components/HeaderDetailPage.vue'))

/** Genome release string values. */
type GenomeRelease = 'grch37' | 'grch38'

export interface Props {
genomeRelease?: string
genomeRelease?: GenomeRelease
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -21,7 +24,7 @@ const router = useRouter()
const genesListStore = useGenesListStore()

const searchTermRef = ref(String(router.currentRoute.value.query.q))
const genomeReleaseRef = ref(props.genomeRelease)
const genomeReleaseRef = ref<GenomeRelease>(props.genomeRelease)

const loadDataToStore = async () => {
await genesListStore.loadData(router.currentRoute.value.query)
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ const SearchBar = defineAsyncComponent(() => import('@/components/SearchBar.vue'

const router = useRouter()

const searchTerm = ref('')
const genomeRelease = ref('grch37')
/** Genome release string values. */
type GenomeRelease = 'grch37' | 'grch38'

const searchTerm = ref<string>('')
const genomeRelease = ref<GenomeRelease>('grch37')
const showCaseInformation = ref(false)

interface Example {
query: string
label?: string
genomeRelease: string
genomeRelease: GenomeRelease
}

const examples: Example[] = [
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/views/__tests__/HomeView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ describe.concurrent('HomeView with mocked router', async () => {
)

const textField = wrapper.find('.v-text-field')
const select = wrapper.find('.v-select')
const searchButton = wrapper.find('#search')
const genomeReleaseButton = wrapper.find('.genome-release-menu')
const searchButton = wrapper.find('.start-search')
expect(textField.exists()).toBe(true)
expect(select.exists()).toBe(true)
expect(genomeReleaseButton.exists()).toBe(true)
expect(searchButton.exists()).toBe(true)
})

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/views/__tests__/StrucvarDetailsView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { VMenu } from 'vuetify/components'

import * as BRCA1GeneInfo from '@/assets/__tests__/BRCA1GeneInfo.json'
import * as CurrentSV from '@/assets/__tests__/ExampleSV.json'
import ClinvarCard from '@/components/GeneDetails/ClinvarCard.vue'
// import ClinvarCard from '@/components/GeneDetails/ClinvarCard.vue'
import ConditionsCard from '@/components/GeneDetails/ConditionsCard.vue'
import ExpressionCard from '@/components/GeneDetails/ExpressionCard.vue'
import OverviewCard from '@/components/GeneDetails/OverviewCard.vue'
import PathogenicityCard from '@/components/GeneDetails/PathogenicityCard.vue'
import GenomeBrowser from '@/components/GenomeBrowser.vue'
import HeaderDetailPage from '@/components/HeaderDetailPage.vue'
import SearchBar from '@/components/SearchBar.vue'
import ClinsigCard from '@/components/StrucvarDetails/ClinsigCard.vue'
// import ClinsigCard from '@/components/StrucvarDetails/ClinsigCard.vue'
import StrucvarClinvarCard from '@/components/StrucvarDetails/ClinvarCard.vue'
import GeneListCard from '@/components/StrucvarDetails/GeneListCard.vue'
import VariantToolsCard from '@/components/StrucvarDetails/VariantToolsCard.vue'
Expand Down