Skip to content

Commit

Permalink
feat: seqvars effects filter (#1833)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregoor authored Jul 24, 2024
1 parent 8d39e44 commit 38febb1
Show file tree
Hide file tree
Showing 25 changed files with 393 additions and 141 deletions.
8 changes: 4 additions & 4 deletions frontend/end-to-end-tests/seqvar-filtration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ test.describe('genotype', () => {
test('any is checked when all types are checked', async ({ page }) => {
const firstAnyButton = page
.getByRole('button', { name: 'any' })
.and(page.locator('[aria-selected]'))
.and(page.locator('[aria-checked]'))
.first()
await expect(firstAnyButton).toHaveAttribute('aria-selected', 'false')
await expect(firstAnyButton).toHaveAttribute('aria-checked', 'false')
for (const name of ['0/0', '1/0', '1/1']) {
const button = page.getByRole('button', { name }).first()
if ((await button.getAttribute('aria-selected')) === 'false') {
if ((await button.getAttribute('aria-checked')) === 'false') {
await button.click()
}
}
await expect(firstAnyButton).toHaveAttribute('aria-selected', 'true')
await expect(firstAnyButton).toHaveAttribute('aria-checked', 'true')
})
})

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/cases/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@
font-weight: 400;
line-height: 20px;
}

.v-selection-control .v-label {
margin-bottom: 0;
}
208 changes: 208 additions & 0 deletions frontend/src/seqvars/components/EffectsSelect/EffectsSelect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<script setup lang="ts">
import { computed } from 'vue'
import {
SeqvarsQueryPresetsConsequence,
SeqvarsTranscriptTypeChoiceList,
SeqvarsVariantConsequenceChoiceList,
SeqvarsVariantTypeChoiceList,
} from '@varfish-org/varfish-api/lib'
import { Query } from '@/seqvars/types'
import CheckButton from '../ui/CheckButton.vue'
import CollapsibleGroup from '../ui/CollapsibleGroup.vue'
import Input from '../ui/Input.vue'
import PresetSelect from '../ui/PresetSelect.vue'
import { matchesEffectsPreset } from './utils'
const VARIANT_TYPES = {
snv: 'SNV',
indel: 'indel',
mnv: 'MNV',
complex_substitution: 'complex substitution',
} satisfies Record<SeqvarsVariantTypeChoiceList[number], string>
const TRANSCRIPT_TYPES = {
coding: 'coding',
non_coding: 'non-coding',
} satisfies Record<SeqvarsTranscriptTypeChoiceList[number], string>
const CUSTOMIZATION = {
Coding: {
disruptive_inframe_deletion: 'disruptive in-frame deletion',
disruptive_inframe_insertion: 'disruptive in-frame insertion',
// feature_truncation: 'feature truncation',
// frameshift_elongation: 'frameshift elongation',
frameshift_variant: 'frameshift variant',
// inframe_deletion: 'inframe deletion',
// inframe_insertion: 'inframe insertion',
// internal_elongation: 'internal elongation',
missense_variant: 'missense',
// mnv: 'MNV',
start_lost: 'start lost',
stop_gained: 'stop gained',
stop_retained_variant: 'stop retained',
stop_lost: 'stop lost',
// tandem_duplication: 'tandem duplication',
},
'Off-Exome': {
downstream_gene_variant: 'downstream',
intron_variant: 'intronic (coding transcript)',
// intergenic: 'intergenic',
upstream_gene_variant: 'upstream',
// exon_loss: 'exon loss',
},
'Non-coding': {
'3_prime_UTR_variant-exon_variant': "3' UTR exonic",
'3_prime_UTR_variant-intron_variant': "3' UTR intronic",
'5_prime_UTR_variant-exon_variant': "5' UTR exonic",
'5_prime_UTR_variant-intron_variant': "5' UTR intronic",
non_coding_transcript_exon_variant: 'non-coding exonic',
non_coding_transcript_intron_variant: 'non-coding intronic',
},
Splicing: {
splice_acceptor_variant: 'splice acceptor',
splice_donor_variant: 'splice donor',
splice_region_variant: 'splice region',
},
// Structural: {
// structural: 'structural ',
// transcript_ablation: 'transcript ablation',
// },
} satisfies Record<
string,
Partial<Record<SeqvarsVariantConsequenceChoiceList[number], string>>
>
const model = defineModel<Query>({ required: true })
const { presets } = defineProps<{
presets: SeqvarsQueryPresetsConsequence[]
}>()
const maxExonDistance = computed({
get: () => model.value.consequence.max_distance_to_exon,
set(value) {
model.value.consequence.max_distance_to_exon = value
},
})
const toggle = (arr: string[] | undefined, key: string) => {
if (arr == undefined) {
return
}
const index = arr.indexOf(key)
if (index === -1) {
arr.push(key)
} else {
arr.splice(index, 1)
}
}
</script>

<template>
<CollapsibleGroup title="Effects">
<div style="display: flex; flex-direction: column; gap: 8px">
<PresetSelect
v-model="model"
:presets="presets"
preset-id-field="consequencepresets"
settings-field="consequence"
:matcher="matchesEffectsPreset"
/>

<div>
<label style="margin: 0; font-size: var(--font-size-sm)" for="max-exon">
Max distance to next exon</label
>
<div
style="
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
"
>
<CheckButton
:model-value="
maxExonDistance == null || maxExonDistance == undefined
"
@update:model-value="maxExonDistance = null"
>
any
</CheckButton>
<CheckButton
:model-value="maxExonDistance == 20"
@update:model-value="maxExonDistance = 20"
>
20
</CheckButton>
<CheckButton
:model-value="maxExonDistance == 100"
@update:model-value="maxExonDistance = 100"
>
100
</CheckButton>
<Input
id="max-exon"
v-model="maxExonDistance"
type="number"
style="max-width: 100px"
/>
</div>
</div>

<div>
<div>Variant Type</div>
<v-checkbox
v-for="(label, key) in VARIANT_TYPES"
:key="key"
:label="label"
:hide-details="true"
density="compact"
:model-value="model.consequence.variant_types?.includes(key)"
@update:model-value="toggle(model.consequence.variant_types, key)"
/>
</div>
<div>
<div>Transcript Type</div>
<v-checkbox
v-for="(label, key) in TRANSCRIPT_TYPES"
:key="key"
:label="label"
:hide-details="true"
density="compact"
style="font-size: var(--font-size-sm)"
:model-value="model.consequence.transcript_types?.includes(key)"
@update:model-value="toggle(model.consequence.transcript_types, key)"
/>
</div>
<CollapsibleGroup title="Customize effects">
<div style="display: flex; flex-direction: column; gap: 8px">
<div v-for="(fields, title) in CUSTOMIZATION">
{{ title }}

<v-checkbox
v-for="(label, key) in fields"
:key="key"
:label="label"
:hide-details="true"
density="compact"
style="font-size: var(--font-size-sm)"
:model-value="
model.consequence.variant_consequences?.includes(key)
"
@update:model-value="
toggle(model.consequence.variant_consequences, key)
"
/>
</div>
</div>
</CollapsibleGroup>
</div>
</CollapsibleGroup>
</template>
21 changes: 21 additions & 0 deletions frontend/src/seqvars/components/EffectsSelect/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import isEqual from 'fast-deep-equal/es6'

import { LocalFields } from '@/seqvars/types'
import {
SeqvarsQueryPresetsConsequence,
SeqvarsQuerySettingsConsequence,
} from '@varfish-org/varfish-api/lib'

export function matchesEffectsPreset(
value: LocalFields<SeqvarsQuerySettingsConsequence>,
preset: SeqvarsQueryPresetsConsequence,
) {
return isEqual(
...([value, preset].map((v) => [
v.max_distance_to_exon,
new Set(v.variant_types),
new Set(v.transcript_types),
new Set(v.variant_consequences),
]) as [unknown, unknown]),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computed, defineProps } from 'vue'
import { SeqvarsQuerySettingsFrequency } from '@varfish-org/varfish-api/lib'
import Input from '@/seqvars/components/Input.vue'
import Input from '../ui/Input.vue'
type ValueOf<T> = T[keyof T]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
<script setup lang="ts">
import { SeqvarsQueryPresetsFrequency } from '@varfish-org/varfish-api/lib'
import CollapsibleGroup from '@/seqvars/components/CollapsibleGroup.vue'
import Hr from '@/seqvars/components/Hr.vue'
import Item from '@/seqvars/components/Item.vue'
import CollapsibleGroup from '../ui/CollapsibleGroup.vue'
import PresetSelect from '../ui/PresetSelect.vue'
import { Query } from '@/seqvars/types'
import { copy } from '@/varfish/helpers'
import FrequencyControls from './FrequencyControls.vue'
import { matchesFrequencyPreset } from './utils'
const { presets } = defineProps<{ presets: SeqvarsQueryPresetsFrequency[] }>()
const model = defineModel<Query>({ required: true })
const setToPreset = (preset: SeqvarsQueryPresetsFrequency) => {
model.value.frequencypresets = preset.sodar_uuid
model.value.frequency = copy(preset)
}
</script>

<template>
Expand All @@ -26,23 +20,13 @@ const setToPreset = (preset: SeqvarsQueryPresetsFrequency) => {
"
>
<div style="width: 100%; display: flex; flex-direction: column; gap: 4px">
<div
role="listbox"
style="width: 100%; display: flex; flex-direction: column"
>
<Item
v-for="preset in presets"
:key="preset.sodar_uuid"
:selected="preset.sodar_uuid === model.frequencypresets"
:modified="!matchesFrequencyPreset(model.frequency, preset)"
@click="() => setToPreset(preset)"
@revert="() => setToPreset(preset)"
>
{{ preset.label }}
</Item>
</div>

<Hr />
<PresetSelect
v-model="model"
:presets="presets"
preset-id-field="frequencypresets"
settings-field="frequency"
:matcher="matchesFrequencyPreset"
/>

<FrequencyControls v-model="model.frequency" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
} from '@varfish-org/varfish-api/lib'
import { copy } from '@/varfish/helpers'
import CollapsibleGroup from '@/seqvars/components/CollapsibleGroup.vue'
import Hr from '@/seqvars/components/Hr.vue'
import Item from '@/seqvars/components/Item.vue'
import CollapsibleGroup from '../ui/CollapsibleGroup.vue'
import Hr from '../ui/Hr.vue'
import Item from '../ui/Item.vue'
import { Query } from '@/seqvars/types'
import { Affected, GENOTYPE_PRESETS, SexAssignedAtBirth } from './constants'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SeqvarsSampleGenotypeChoiceList,
} from '@varfish-org/varfish-api/lib'
import CheckButton from './CheckButton.vue'
import CheckButton from '../ui/CheckButton.vue'
enum InheritanceMode {
WILD_TYPE = 'wild-type',
Expand Down
Loading

0 comments on commit 38febb1

Please sign in to comment.