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