Skip to content
Snippets Groups Projects
Commit 52642b7b authored by Simon Malesys's avatar Simon Malesys
Browse files

Add a search by motif

parent 6af167cb
No related branches found
No related tags found
1 merge request!8Merge modifications asked by reviewers
Pipeline #142398 failed
......@@ -35,7 +35,14 @@
<h2>ADVANCED SEARCH</h2>
</header>
<div class="options-group">
<section>
<h3>
SEQUENCE MOTIF
</h3>
<MotifInput v-model="filters.motif"></MotifInput>
</section>
<section>
<h3>
ORGANISM
<button
......@@ -61,9 +68,9 @@
</AppCheckbox>
</div>
</div>
</div>
</section>
<div class="options-group">
<section>
<h3>
SOURCES
<button
......@@ -89,9 +96,9 @@
</AppCheckbox>
</div>
</div>
</div>
</section>
<div class="options-group">
<section>
<h3>
HEAVY CHAIN GENE SEGMENTS
<button
......@@ -117,9 +124,9 @@
</AppCheckbox>
</div>
</div>
</div>
</section>
<div class="options-group">
<section>
<h3>
LIGHT CHAIN GENE SEGMENTS
<button
......@@ -145,7 +152,7 @@
</AppCheckbox>
</div>
</div>
</div>
</section>
<footer>
<button
......@@ -165,6 +172,7 @@ import AppDialog from './AppDialog.vue'
import { useStore } from '../store'
import { type AdvancedFilters, type FilterChip } from '../types'
import AppCheckbox from './AppCheckbox.vue';
import MotifInput from './MotifInput.vue';
onMounted(() => {
if (store.antibodiesSources.length === 0) {
......@@ -184,7 +192,8 @@ const filters: Ref<AdvancedFilters> = ref({
species: [],
sources: [],
heavySegments: [],
lightSegments: []
lightSegments: [],
motif: ""
})
const sourcesList = computed(() => store.antibodiesSources)
const speciesList = computed(() => store.antibodiesSpecies)
......@@ -240,7 +249,7 @@ function updateFilters(): void {
* @param filter - The name of the filter group
* @param list - The list of values to select
*/
function selectAll(filter: keyof AdvancedFilters, list: Readonly<Array<string>>): void {
function selectAll(filter: keyof Omit<AdvancedFilters, "motif">, list: Readonly<Array<string>>): void {
if (filters.value[filter].length === list.length) {
filters.value[filter] = []
} else {
......@@ -308,7 +317,7 @@ function removeFilter(filterChip: FilterChip): void {
margin-top: 0;
}
.options-group h3 {
.advanced-search-dialog section h3 {
align-items: center;
display: flex;
gap: var(--spacing);
......
......@@ -279,6 +279,7 @@ function download(): void {
sources: request.value.sources,
heavySegments: request.value.heavySegments,
lightSegments: request.value.lightSegments,
motif: request.value.motif,
format: downloadFormat.value
}
......
<template>
<div class="motif">
<input
id="motif-input"
v-model="model"
type="text"
placeholder="e.g., GLLF, ACKC.."
spellcheck="false"
pattern="[A-Z,]*">
<button
type="button"
class="clear-input"
@click="clearInput()">
CLEAR
</button>
</div>
</template>
<script lang="ts" setup>
const model = defineModel<string>()
function clearInput(): void {
model.value = ''
}
</script>
<style scoped>
.motif {
display: flex;
flex-flow: row nowrap;
gap: var(--spacing);
padding-bottom: var(--spacing);
}
#motif-input {
background: none;
border: none;
border-bottom: 1px var(--white) solid;
border-radius: var(--radius) var(--radius) 0 0;
color: var(--primary);
font-size: var(--spacing);
padding: var(--half-spacing);
transition: background-color 0.2s;
width: 100%;
&:focus {
background-color: var(--primary-translucent);
outline: none;
border-bottom-color: var(--primary);
}
&:not(:valid) {
color: var(--red);
border-bottom-color: var(--red);
}
&:not(:valid):focus {
background-color: var(--red-translucent);
}
}
</style>
......@@ -41,7 +41,8 @@ const filters: Ref<AdvancedFilters> = ref({
species: [],
sources: [],
heavySegments: [],
lightSegments: []
lightSegments: [],
motif: ""
})
/**
......@@ -79,13 +80,18 @@ function fetchAntibodies (): void {
store.request.lightSegments = filters.value.lightSegments.join(',')
} else delete store.request.lightSegments
if (filters.value.motif.length > 0) {
store.request.motif = filters.value.motif
} else delete store.request.motif
store.fetchAntibodies(store.request)
store.countAntibodies({
keywords: store.request.keywords,
species: store.request.species,
sources: store.request.sources,
heavySegments: store.request.heavySegments,
lightSegments: store.request.lightSegments
lightSegments: store.request.lightSegments,
motif: store.request.motif
})
}
</script>
......@@ -104,7 +110,7 @@ function fetchAntibodies (): void {
.search-bar {
border-bottom: 1px var(--white) solid;
display: flex;
gap: var(--spacing);
gap: var(--half-spacing);
padding-bottom: var(--half-spacing);
transition: border-bottom-color .5s;
}
......@@ -126,7 +132,7 @@ function fetchAntibodies (): void {
font-size: var(--spacing);
padding: 0 var(--half-spacing);
min-width: 0;
transition: background-color 0.1s;
transition: background-color 0.2s;
&:focus {
background-color: var(--primary-translucent);
......
......@@ -11,6 +11,7 @@
--primary: hsl(252, 100%, 86%);
--primary-translucent: hsl(252, 100%, 86%, 0.3);
--red: hsl(0, 69%, 50%);
--red-translucent: hsl(0, 69%, 50%, 0.3);
--purple: hsl(252, 70%, 48%);
--black: hsl(0, 0%, 4%);
--black-translucent: hsla(0, 0%, 4%, 0.5);
......
......@@ -47,7 +47,8 @@ interface APIFiltersParams {
species?: string
sources?: string
heavySegments?: string,
lightSegments?: string
lightSegments?: string,
motif?: string
}
/**
......@@ -118,7 +119,8 @@ export interface AdvancedFilters {
species: Array<Antibody['species']>
sources: Array<FastaHeader['source']>
heavySegments: Array<FastaHeader['vGeneSegment']>
lightSegments: Array<FastaHeader['vGeneSegment']>
lightSegments: Array<FastaHeader['vGeneSegment']>,
motif: string
}
/**
......
......@@ -72,6 +72,19 @@ export default function buildDBQuery(request, reply, done) {
})
}
if (request.query.motif) {
const $or = []
request.query.motif
.forEach(motif => {
// TODO: test if removing the 'i' options is faster
$or.push({ 'heavyChain.sequence': { $regex: motif.trim(), $options: 'i' } })
$or.push({ 'ligthChain.sequence': { $regex: motif.trim(), $options: 'i' } })
})
where.$and.push({ $or })
}
// Empty $and operators raise an error in MongoDB
// so where needs to remain empty as well.
if (where.$and.length !== 0) request.where = where
......
......@@ -20,6 +20,11 @@
* for the ones that are not simple strings.
*/
export default function parseRequest(request, reply, done) {
console.log(JSON.stringify(request.query, null, 2))
if (request.query.motif) {
request.query.motif = request.query.motif.trim().split(',')
}
if (request.query.keywords) {
request.query.keywords = request.query.keywords.trim().split(',')
}
......
......@@ -88,6 +88,13 @@ export default {
items: {
type: 'string',
}
},
motif: {
description: 'The motif to search for in the sequences',
type: 'array',
items: {
type: 'string',
}
}
}
},
......
......@@ -155,6 +155,13 @@ export default {
items: {
type: 'string',
}
},
motif: {
description: 'The motif to search for in the sequences',
type: 'array',
items: {
type: 'string',
}
}
}
}
......
......@@ -100,6 +100,13 @@ export default {
type: 'string',
}
},
motif: {
description: 'The motif to search for in the sequences',
type: 'array',
items: {
type: 'string',
}
},
limit: {
description: 'The maximum number of antibodies to return. Used mainly for pagination.',
type: 'integer',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment