Skip to content
Snippets Groups Projects
Commit 0ed02dc1 authored by Remi  PLANEL's avatar Remi PLANEL
Browse files

Multiple slider

parent cc3b1fa7
No related branches found
No related tags found
1 merge request!168Multiple slider
...@@ -354,10 +354,32 @@ build:prod:wiki: ...@@ -354,10 +354,32 @@ build:prod:wiki:
- if: $CI_COMMIT_BRANCH == "main" - if: $CI_COMMIT_BRANCH == "main"
build-wiki:dev:
stage: build-wiki
needs:
- set-meili-env:dev
rules:
- if: $CI_COMMIT_BRANCH != "main"
image: node:21.1-bookworm-slim
variables:
NODE_OPTIONS: --max_old_space_size=12288
NUXT_APP_BASE_URL: /wiki/
NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL: ${MEILI_HOST}
NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY: ${MEILI_API_KEY}
NUXT_PUBLIC_MEILI_HOST: ${MEILI_HOST}
NUXT_PUBLIC_MEILI_API_KEY: ${MEILI_API_KEY}
before_script:
- npm install
script:
- npm run generate
artifacts:
paths:
- .output/public
build-wiki:prod: build-wiki:prod:
stage: build-wiki stage: build-wiki
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs: needs:
- set-meili-env:prod - set-meili-env:prod
- get-zotero - get-zotero
...@@ -381,6 +403,9 @@ build-wiki:prod: ...@@ -381,6 +403,9 @@ build-wiki:prod:
load-website:dev: load-website:dev:
image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION
stage: load-website stage: load-website
needs:
- build-wiki:dev
- deploy:dev
variables: variables:
NAMESPACE: "defense-finder-dev" NAMESPACE: "defense-finder-dev"
environment: environment:
...@@ -388,9 +413,13 @@ load-website:dev: ...@@ -388,9 +413,13 @@ load-website:dev:
rules: rules:
- if: $CI_COMMIT_BRANCH != "main" - if: $CI_COMMIT_BRANCH != "main"
script: script:
- kubectl wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s --namespace ${NAMESPACE} - kubectl --namespace ${NAMESPACE} wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s
- echo "Le pod est ready" - echo "Le pod est ready"
- WIKI_POD=$(kubectl --namespace ${NAMESPACE} get pods -l "app.kubernetes.io/name=df-wiki" --output jsonpath='{.items[0].metadata.name}')
- kubectl --namespace ${NAMESPACE} cp .output/public/ ${WIKI_POD}:/website
- |
kubectl --namespace ${NAMESPACE}
exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && find * -type d -exec sh -c "for d in "$@"; do (cd "/usr/share/nginx/html/$d"; cp --archive --recursive --symbolic-link /structure-data/sanitized-dump/$d/* .) done" argv0 {} +'
load-website:prod: load-website:prod:
...@@ -415,6 +444,8 @@ load-website:prod: ...@@ -415,6 +444,8 @@ load-website:prod:
exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && find * -type d -exec sh -c "for d in "$@"; do (cd "/usr/share/nginx/html/$d"; cp --archive --recursive --symbolic-link /structure-data/sanitized-dump/$d/* .) done" argv0 {} +' exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && find * -type d -exec sh -c "for d in "$@"; do (cd "/usr/share/nginx/html/$d"; cp --archive --recursive --symbolic-link /structure-data/sanitized-dump/$d/* .) done" argv0 {} +'
################ DEPLOY ########################## ################ DEPLOY ##########################
.deploy: .deploy:
stage: deploy stage: deploy
......
<script setup lang="ts"> <script setup lang="ts">
// import type { FacetDistribution } from "meilisearch"; // import type { FacetDistribution } from "meilisearch";
import { useCsvDownload } from "@/composables/useCsvDownload" import { useCsvDownload } from "@/composables/useCsvDownload"
import { useNumericalFilter } from "@/composables/useNumericalfilter"
import { useSlots } from 'vue' import { useSlots } from 'vue'
import { useDisplay } from "vuetify"; import { useDisplay } from "vuetify";
import * as Plot from "@observablehq/plot"; import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure"; import PlotFigure from "~/components/PlotFigure";
import * as d3 from "d3";
import { useThrottleFn } from '@vueuse/core' import { useThrottleFn } from '@vueuse/core'
import * as d3 from "d3";
import { useMeiliSearch } from "#imports" import { useMeiliSearch } from "#imports"
// import { saveAs } from "file-saver"; // import { saveAs } from "file-saver";
import { useFileSystemAccess } from '@vueuse/core' import { useFileSystemAccess } from '@vueuse/core'
...@@ -15,13 +18,22 @@ export interface SortItem { ...@@ -15,13 +18,22 @@ export interface SortItem {
order: boolean | 'asc' | 'desc' order: boolean | 'asc' | 'desc'
} }
export interface NumericalFilter {
id: string
name: string
range: [number, number]
}
export interface NumericalFilterModel extends NumericalFilter {
model: [number, number]
}
export interface Props { export interface Props {
title?: string title?: string
db?: string db?: string
sortBy?: SortItem[] sortBy?: SortItem[]
facets: MaybeRef<string[]> facets: MaybeRef<string[]>
dataTableServerProps: Record<string, any> dataTableServerProps: Record<string, any>
columnsToDownload: MaybeRef<string[]> columnsToDownload?: MaybeRef<string[] | undefined>
} }
export interface FilterItem { export interface FilterItem {
...@@ -36,7 +48,9 @@ export interface FilterItem { ...@@ -36,7 +48,9 @@ export interface FilterItem {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
title: '', title: '',
db: 'refseq', db: 'refseq',
columnsToDownload: undefined,
sortBy: () => [{ key: "type", order: "asc" }], sortBy: () => [{ key: "type", order: "asc" }],
}); });
...@@ -61,7 +75,7 @@ const computedTableHeight = computed(() => { ...@@ -61,7 +75,7 @@ const computedTableHeight = computed(() => {
const computedHeight = height.value - 350 const computedHeight = height.value - 350
return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value
}) })
const plddtRange = ref([0, 100])
const pendingDownloadData = ref(false) const pendingDownloadData = ref(false)
const filterInputValues = computed(() => { const filterInputValues = computed(() => {
...@@ -111,39 +125,23 @@ onMounted(async () => { ...@@ -111,39 +125,23 @@ onMounted(async () => {
searchOrFilter() searchOrFilter()
}) })
const hasPlddt = computed(() => props.db === 'structure')
// Fetch results
const plddtFilter = computed(() => {
const plddtRangeValue = plddtRange.value
if (hasPlddt.value && plddtRangeValue?.length === 2 && (plddtRangeValue[0] !== 0 || plddtRangeValue[1] !== 100)) {
return `plddts ${plddtRangeValue[0]} TO ${plddtRangeValue[1]}`
} else {
return undefined
}
}) const { range: plddtsRange, stringifyFilter: plddtsFilter, reset: plddtsReset } = useNumericalFilter("plddts", 0, 100)
const { range: iptmRange, stringifyFilter: iptmFilter, reset: iptmReset } = useNumericalFilter("iptm+ptm", 0, 1)
const { range: pdockqRange, stringifyFilter: pdockqFilter, reset: pdockqReset } = useNumericalFilter("pDockQ", 0, 1)
const computedFilter = computed(() => { const computedFilter = computed(() => {
if (toValue(msFilter)) { return [toValue(msFilter), toValue(plddtsFilter), toValue(iptmFilter), toValue(pdockqFilter)].filter(f => f !== undefined).join(" AND ")
if (toValue(plddtFilter)) {
return `${toValue(msFilter)} AND ${toValue(plddtFilter)}`
}
else {
return toValue(msFilter)
}
} else {
if (toValue(plddtFilter)) {
return `${toValue(plddtFilter)}`
}
else {
return undefined
}
}
}) })
watch(computedFilter, () => {
searchOrFilter()
})
const msError = computed(() => { const msError = computed(() => {
if (filterError.value?.type && filterError.value?.message) { if (filterError.value?.type && filterError.value?.message) {
...@@ -151,8 +149,9 @@ const msError = computed(() => { ...@@ -151,8 +149,9 @@ const msError = computed(() => {
} else { return false } } else { return false }
}) })
const throttleSearch = useThrottleFn(async () => { searchOrFilter() }, 300) const throttleSearch = useThrottleFn(async () => {
searchOrFilter()
}, 300)
async function searchOrFilter(pagination = true) { async function searchOrFilter(pagination = true) {
// do something, it will be called at most 1 time per second // do something, it will be called at most 1 time per second
...@@ -276,13 +275,7 @@ const autocompleteItems = computed(() => { ...@@ -276,13 +275,7 @@ const autocompleteItems = computed(() => {
} }
}) })
// const canAddTextSearch = computed(() => {
// if (filterOrSearch.value !== null && filterOrSearch.value.length > 0) {
// const lastItem = filterOrSearch.value.slice(-1)[0]
// return lastItem?.props.type === 'value' || lastItem?.props.type === "text"
// }
// return true
// })
function selectItem(item) { function selectItem(item) {
filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item] filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item]
...@@ -319,19 +312,33 @@ async function downloadData() { ...@@ -319,19 +312,33 @@ async function downloadData() {
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
<v-row v-if="props.db === 'structure'"> <v-row v-if="props.db === 'structure'">
<v-col>
<v-range-slider v-model="plddtRange" density="compact" hide-details="auto" label="pLDDT" step="0.1" <v-col md="12" lg="4">
@update:modelValue="throttleSearch()"> <v-range-slider v-model="plddtsRange" strict density="compact" hide-details="auto" label="pLDDT"
<template v-slot:prepend> step="0.5" :min="0" :max="100" thumb-label="always" @update:modelValue="throttleSearch()">
<span hide-details single-line type="number" variant="outlined" density="compact" <template #append>
style="width: 70px">{{ plddtRange[0] }}</span> <v-btn variant="text" icon="md:restart_alt" @click="plddtsReset()"></v-btn>
</template> </template>
<template v-slot:append> </v-range-slider>
<span hide-details single-line type="number" variant="outlined" style="width: 70px" </v-col>
density="compact">{{ plddtRange[1] }}</span> <v-col md="12" lg="4">
<v-range-slider v-model="iptmRange" strict density="compact" hide-details="auto" label="iptm+ptm"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="throttleSearch()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="iptmReset()"></v-btn>
</template> </template>
</v-range-slider>
</v-col>
<!-- pdockqReset -->
<v-col md="12" lg="4">
<v-range-slider v-model="pdockqRange" strict density="compact" hide-details="auto" label="pDockQ"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="throttleSearch()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="pdockqReset()"></v-btn>
</template>
</v-range-slider> </v-range-slider>
</v-col> </v-col>
</v-row> </v-row>
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import * as Plot from "@observablehq/plot"; import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure"; import PlotFigure from "~/components/PlotFigure";
import type { SortItem } from "@/components/ServerDbTable.vue" import type { SortItem } from "@/components/ServerDbTable.vue"
import { ServerDbTable } from "#components" import { ServerDbTable } from "#components"
const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }]) const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }])
const itemValue = ref("id"); const itemValue = ref("id");
...@@ -24,6 +25,7 @@ const headers: Ref<Object[]> = ref([ ...@@ -24,6 +25,7 @@ const headers: Ref<Object[]> = ref([
// { title: "Type", key: "type", removable: true }, // { title: "Type", key: "type", removable: true },
]) ])
const { search: msSearch, result: msResult } = useMeiliSearch('structure') const { search: msSearch, result: msResult } = useMeiliSearch('structure')
const defaultDataTableServerProps = ref({ const defaultDataTableServerProps = ref({
...@@ -74,6 +76,9 @@ const plddtDistribution = computed(() => { ...@@ -74,6 +76,9 @@ const plddtDistribution = computed(() => {
} }
}) })
function remove(key) { function remove(key) {
headers.value = headers.value.filter(header => header.key !== key) headers.value = headers.value.filter(header => header.key !== key)
} }
...@@ -83,14 +88,6 @@ function remove(key) { ...@@ -83,14 +88,6 @@ function remove(key) {
<ServerDbTable title="Predicted Structures" db="structure" :sortBy="sortBy" :facets="facets" <ServerDbTable title="Predicted Structures" db="structure" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps"> :data-table-server-props="dataTableServerProps">
<!-- <template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
Predicted Structures
</v-toolbar-title><v-spacer></v-spacer>
</v-toolbar>
</template> -->
<template #[`item.proteins_in_the_prediction`]="{ item }"> <template #[`item.proteins_in_the_prediction`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.fasta_file)"> <CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.fasta_file)">
</CollapsibleChips> </CollapsibleChips>
......
export function useNumericalFilter(
id: MaybeRef<string>,
min: MaybeRef<number>,
max: MaybeRef<number>,
) {
const range: Ref<[number, number]> = ref([toValue(min), toValue(max)])
const stringifyFilter: Ref<string | undefined> = ref(`${toValue(id)} ${toValue(min)} TO ${toValue(max)}`)
watchEffect(() => {
console.log("watch reange")
console.log(range.value)
if (range.value[0] === toValue(min) && range.value[1] === toValue(max)) {
stringifyFilter.value = undefined
} else {
stringifyFilter.value = `'${toValue(id)}' ${range.value[0]} TO ${range.value[1]}`
}
})
function reset() {
range.value = [toValue(min), toValue(max)]
}
// watch(() => range, () => {
// console.log("watch reange")
// console.log(range)
// if (range.value[0] === toValue(min) && range.value[1] === toValue(max)) {
// stringifyFilter.value = undefined
// } else {
// stringifyFilter.value = `${toValue(id)} ${toValue(min)} TO ${toValue(max)}`
// }
// }, { deep: true })
return { range, stringifyFilter, reset }
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment