From 0ed02dc133f384d15eb65902879e1d591830d62d Mon Sep 17 00:00:00 2001
From: Remi  PLANEL <rplanel@pasteur.fr>
Date: Thu, 21 Dec 2023 21:37:50 +0100
Subject: [PATCH] Multiple slider

---
 .gitlab-ci.yml                     | 37 ++++++++++-
 components/ServerDbTable.vue       | 99 ++++++++++++++++--------------
 components/content/StructureDb.vue | 13 ++--
 composables/useNumericalfilter.ts  | 41 +++++++++++++
 4 files changed, 133 insertions(+), 57 deletions(-)
 create mode 100644 composables/useNumericalfilter.ts

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5e8e5a82..be759a1c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -354,10 +354,32 @@ build:prod:wiki:
     - 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:
   stage: build-wiki
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main"
   needs:
     - set-meili-env:prod
     - get-zotero
@@ -381,6 +403,9 @@ build-wiki:prod:
 load-website:dev:
   image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION
   stage: load-website
+  needs: 
+    - build-wiki:dev
+    - deploy:dev
   variables:
     NAMESPACE: "defense-finder-dev"
   environment:
@@ -388,9 +413,13 @@ load-website:dev:
   rules:
     - if: $CI_COMMIT_BRANCH != "main"
   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"
-
+    - 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:
@@ -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 {} +'
 
 
+
+
 ################ DEPLOY ##########################
 .deploy:
   stage: deploy
diff --git a/components/ServerDbTable.vue b/components/ServerDbTable.vue
index 95a2402f..43664072 100644
--- a/components/ServerDbTable.vue
+++ b/components/ServerDbTable.vue
@@ -1,12 +1,15 @@
 <script setup lang="ts">
 // import type { FacetDistribution } from "meilisearch";
 import { useCsvDownload } from "@/composables/useCsvDownload"
+import { useNumericalFilter } from "@/composables/useNumericalfilter"
+
 import { useSlots } from 'vue'
 import { useDisplay } from "vuetify";
 import * as Plot from "@observablehq/plot";
 import PlotFigure from "~/components/PlotFigure";
-import * as d3 from "d3";
 import { useThrottleFn } from '@vueuse/core'
+
+import * as d3 from "d3";
 import { useMeiliSearch } from "#imports"
 // import { saveAs } from "file-saver";
 import { useFileSystemAccess } from '@vueuse/core'
@@ -15,13 +18,22 @@ export interface SortItem {
     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 {
     title?: string
     db?: string
     sortBy?: SortItem[]
     facets: MaybeRef<string[]>
     dataTableServerProps: Record<string, any>
-    columnsToDownload: MaybeRef<string[]>
+    columnsToDownload?: MaybeRef<string[] | undefined>
 }
 
 export interface FilterItem {
@@ -36,7 +48,9 @@ export interface FilterItem {
 const props = withDefaults(defineProps<Props>(), {
     title: '',
     db: 'refseq',
+    columnsToDownload: undefined,
     sortBy: () => [{ key: "type", order: "asc" }],
+
 });
 
 
@@ -61,7 +75,7 @@ const computedTableHeight = computed(() => {
     const computedHeight = height.value - 350
     return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value
 })
-const plddtRange = ref([0, 100])
+
 const pendingDownloadData = ref(false)
 
 const filterInputValues = computed(() => {
@@ -111,39 +125,23 @@ onMounted(async () => {
     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(() => {
-    if (toValue(msFilter)) {
-        if (toValue(plddtFilter)) {
-            return `${toValue(msFilter)} AND ${toValue(plddtFilter)}`
-        }
-        else {
-            return toValue(msFilter)
-        }
-    } else {
-        if (toValue(plddtFilter)) {
-            return `${toValue(plddtFilter)}`
-        }
-        else {
-            return undefined
-        }
-    }
+    return [toValue(msFilter), toValue(plddtsFilter), toValue(iptmFilter), toValue(pdockqFilter)].filter(f => f !== undefined).join(" AND ")
 })
 
 
+watch(computedFilter, () => {
+    searchOrFilter()
+})
+
 
 const msError = computed(() => {
     if (filterError.value?.type && filterError.value?.message) {
@@ -151,8 +149,9 @@ const msError = computed(() => {
     } else { return false }
 })
 
-const throttleSearch = useThrottleFn(async () => { searchOrFilter() }, 300)
-
+const throttleSearch = useThrottleFn(async () => {
+    searchOrFilter()
+}, 300)
 async function searchOrFilter(pagination = true) {
 
     // do something, it will be called at most 1 time per second
@@ -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) {
     filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item]
@@ -319,19 +312,33 @@ async function downloadData() {
         </v-card-text>
         <v-card-text>
             <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"
-                        @update:modelValue="throttleSearch()">
-                        <template v-slot:prepend>
-                            <span hide-details single-line type="number" variant="outlined" density="compact"
-                                style="width: 70px">{{ plddtRange[0] }}</span>
+                <v-col md="12" lg="4">
+                    <v-range-slider v-model="plddtsRange" strict density="compact" hide-details="auto" label="pLDDT"
+                        step="0.5" :min="0" :max="100" thumb-label="always" @update:modelValue="throttleSearch()">
+                        <template #append>
+                            <v-btn variant="text" icon="md:restart_alt" @click="plddtsReset()"></v-btn>
                         </template>
-                        <template v-slot:append>
-                            <span hide-details single-line type="number" variant="outlined" style="width: 70px"
-                                density="compact">{{ plddtRange[1] }}</span>
+                    </v-range-slider>
+                </v-col>
+                <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>
+                    </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-col>
             </v-row>
diff --git a/components/content/StructureDb.vue b/components/content/StructureDb.vue
index 0c54ce0f..8aac1439 100644
--- a/components/content/StructureDb.vue
+++ b/components/content/StructureDb.vue
@@ -2,6 +2,7 @@
 import * as Plot from "@observablehq/plot";
 import PlotFigure from "~/components/PlotFigure";
 import type { SortItem } from "@/components/ServerDbTable.vue"
+
 import { ServerDbTable } from "#components"
 const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }])
 const itemValue = ref("id");
@@ -24,6 +25,7 @@ const headers: Ref<Object[]> = ref([
     // { title: "Type", key: "type", removable: true },
 
 ])
+
 const { search: msSearch, result: msResult } = useMeiliSearch('structure')
 
 const defaultDataTableServerProps = ref({
@@ -74,6 +76,9 @@ const plddtDistribution = computed(() => {
     }
 })
 
+
+
+
 function remove(key) {
     headers.value = headers.value.filter(header => header.key !== key)
 }
@@ -83,14 +88,6 @@ function remove(key) {
     <ServerDbTable title="Predicted Structures" db="structure" :sortBy="sortBy" :facets="facets"
         :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 }">
             <CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.fasta_file)">
             </CollapsibleChips>
diff --git a/composables/useNumericalfilter.ts b/composables/useNumericalfilter.ts
new file mode 100644
index 00000000..9ee97d80
--- /dev/null
+++ b/composables/useNumericalfilter.ts
@@ -0,0 +1,41 @@
+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
-- 
GitLab