Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mdm-lab/wiki
  • hvaysset/wiki
  • jsousa/wiki
  • tclabby/wiki
4 results
Show changes
Commits on Source (31)
Showing
with 446 additions and 416 deletions
......@@ -12,7 +12,7 @@ workflow:
# Functions that should be executed before the build script is run
variables:
HELM_VERSION: "3.9.3"
HELM_VERSION: "3.13.3"
IMAGE_NAME: "df-wiki"
# dev
HOST_DEV: 'defense-finder.dev.pasteur.cloud'
......@@ -31,6 +31,7 @@ cache:
stages:
- delete-release
- build-df-cli
- lint
......@@ -39,9 +40,9 @@ stages:
- update-meilisearch-indexes
- get-meili-key
- build
- build-wiki
# - build-wiki
- deploy
# - load-website
.docker-login: &docker-login
- i=0; while [ "$i" -lt 12 ]; do docker info && break; sleep 5; i=$(( i + 1 )) ; done
......@@ -353,25 +354,94 @@ 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
# 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
# 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:
# name: "k8sdev-01"
# rules:
# - if: $CI_COMMIT_BRANCH != "main"
# script:
# - 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:
# image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION
# stage: load-website
# needs:
# - build-wiki:prod
# - deploy:prod
# environment:
# name: "k8sprod-02"
# variables:
# NAMESPACE: "defense-finder-prod"
# rules:
# - if: $CI_COMMIT_BRANCH == "main"
# script:
# - 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}')
build-wiki:
stage: build-wiki
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
# - kubectl --namespace ${NAMESPACE} cp .output/public/ ${WIKI_POD}:/website
# - kubectl --namespace ${NAMESPACE} cp scripts/copy-structure-data.sh ${WIKI_POD}:/structure-data/sanitized-dump
# - kubectl --namespace ${NAMESPACE} exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && bash copy-structure-data.sh'
......@@ -397,6 +467,16 @@ build-wiki:
--set env="${ENV:-development}"
--values deploy/df-wiki/values.yaml
--values deploy/df-wiki/values.${ENV:-development}.yaml
after_script:
- kubectl --namespace ${KUBE_NAMESPACE} wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s
- echo "Wiki pod is ready"
- WIKI_POD=$(kubectl --namespace ${KUBE_NAMESPACE} get pods -l "app.kubernetes.io/name=df-wiki" --output jsonpath='{.items[0].metadata.name}')
- echo ${WIKI_POD}
- kubectl --namespace ${KUBE_NAMESPACE} cp .output/public/ ${WIKI_POD}:/website
- kubectl --namespace ${KUBE_NAMESPACE} cp scripts/copy-structure-data.sh ${WIKI_POD}:/structure-data/sanitized-dump
- kubectl --namespace ${KUBE_NAMESPACE} exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && bash copy-structure-data.sh'
deploy:dev:
extends: .deploy
......@@ -464,3 +544,5 @@ delete-helm-release:prod:
script:
- echo "Removing $CI_PROJECT_NAME-$CI_ENVIRONMENT_NAME"
- helm delete -n ${NAMESPACE} $CI_PROJECT_NAME-$CI_ENVIRONMENT_NAME
<script setup lang="ts">
// import type { FacetDistribution } from "meilisearch";
import { useCsvDownload } from "@/composables/useCsvDownload"
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'
export interface SortItem {
key: string,
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[]>
numericalFilters?: MaybeRef<string | undefined>
dataTableServerProps: Record<string, any>
columnsToDownload?: MaybeRef<string[] | undefined>
}
export interface FilterItem {
......@@ -32,7 +47,10 @@ export interface FilterItem {
const props = withDefaults(defineProps<Props>(), {
title: '',
db: 'refseq',
columnsToDownload: undefined,
sortBy: () => [{ key: "type", order: "asc" }],
numericalFilters: undefined
});
......@@ -51,18 +69,16 @@ const msFilter: Ref<string | undefined> = ref(undefined)
const page = ref(1)
let loading = ref(false)
const expanded = ref([])
const { height } = useDisplay();
const { height, mobile } = useDisplay();
const minTableHeight = ref(400)
const computedTableHeight = computed(() => {
const computedHeight = height.value - 350
return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value
})
const plddtRange = ref([0, 100])
// const { pending: pendingDownloadData, downloadCsv } = useCsvDownload(props.db, `df-${props.db}`)
const pendingDownloadData = ref(false)
const filterInputValues = computed(() => {
// console.log("recompouted FILTER value")
if (filterOrSearch.value != null) {
return filterOrSearch.value.filter(({ props }) => props.type !== 'text')
} else {
......@@ -70,24 +86,6 @@ const filterInputValues = computed(() => {
}
})
// const queryInputValue = computed(() => {
// if (filterOrSearch.value !== null) {
// const phrase = filterOrSearch.value
// .filter((f) => {
// return f.props.type === 'text'
// })
// .map((f) => {
// return f.value
// })
// if (phrase.length > 1) {
// return `${phrase.join(" ")}`
// }
// else { return phrase[0] }
// } else {
// return null
// }
// })
const isFilter = computed(() => {
return Array.isArray(filterOrSearch.value)
})
......@@ -127,39 +125,18 @@ 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 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), props.numericalFilters].filter(f => f !== undefined).join(" AND ")
})
watch(computedFilter, () => {
searchOrFilter()
})
const msError = computed(() => {
if (filterError.value?.type && filterError.value?.message) {
......@@ -167,8 +144,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
......@@ -292,13 +270,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]
......@@ -314,28 +286,45 @@ function clearSearch() {
search.value = ""
}
async function downloadData() {
pendingDownloadData.value = true
try {
const { data } = await useAsyncMeiliSearch({
index: props.db,
params: { ...toValue(notPaginatedParams), filter: toValue(computedFilter), sort: toValue(msSortBy) },
query: toValue(search),
})
// const groupSortDomain = computed(() => {
// console.log(msResult.value)
// return msResult.value ? d3.groupSort(msResult.value?.hits?.filter((d) => d.phylum), (g) => d3.median(g, (d) => d.phylum), (d) => d.type) : []
// })
useCsvDownload(data, props.columnsToDownload, props.title)
} finally {
pendingDownloadData.value = false
}
}
</script>
<template>
<v-card flat color="transparent">
<v-card-text>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="5">
<v-text-field v-model="search" label="Search..." hide-details prepend-inner-icon="mdi-magnify"
single-line clearable></v-text-field>
</v-col>
<v-col>
<slot name="numerical-filters" :search="throttleSearch"></slot>
</v-card-text>
<v-toolbar flat color="transparent"> </v-toolbar>
<v-data-table-server v-if="!msError" v-model:page="page" color="primary" v-bind="dataTableServerProps"
v-model:items-per-page="hitsPerPage" v-model:sortBy="sortByRef" v-model:expanded="expanded" fixed-header
:loading="loading" :items="msResult?.hits ?? []" :items-length="totalHits" density="compact"
:items-per-page-options="itemsPerPage" :height="computedTableHeight" class="elevation-1 mt-2">
<template #top>
<v-toolbar floating><v-toolbar-title class="text-capitalize">
<v-badge :content="totalHits" color="primary" inline>
<v-btn prepend-icon="md:download" :loading="pendingDownloadData" variant="text" color="primary"
@click="downloadData()">{{
props.title }}
</v-btn>
</v-badge>
</v-toolbar-title>
<v-text-field v-model="search" label="Search..." hide-details="auto" prepend-inner-icon="mdi-magnify"
single-line clearable class="mr-2"></v-text-field>
<v-autocomplete ref="autocompleteInput" hide-details v-model:model-value="filterOrSearch"
auto-select-first chips clearable label="Filter results..." :items="autocompleteItems" single-line
item-value="value" item-title="title" multiple return-object prepend-inner-icon="md:search"
......@@ -352,29 +341,9 @@ function clearSearch() {
</v-list-item>
</template>
</v-autocomplete>
</v-col>
</v-row>
<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>
</template>
<template v-slot:append>
<span hide-details single-line type="number" variant="outlined" style="width: 70px"
density="compact">{{ plddtRange[1] }}</span>
</template>
</v-range-slider>
</v-col>
</v-row>
</v-card-text>
<v-data-table-server v-if="!msError" v-model:page="page" color="primary" v-bind="dataTableServerProps"
v-model:items-per-page="hitsPerPage" v-model:sortBy="sortByRef" v-model:expanded="expanded" fixed-header
:loading="loading" :items="msResult?.hits ?? []" :items-length="totalHits" density="compact"
:items-per-page-options="itemsPerPage" :height="computedTableHeight" class="elevation-1 mt-2">
</v-toolbar>
</template>
<template v-for="(slot, index) of Object.keys(slots)" :key="index" v-slot:[slot]="data">
<slot :name="slot" v-bind="data"></slot>
......
<script setup lang="ts">
import { usePfamStore } from "@/stores/pfam";
export interface Props {
headers: Array<Object>;
systems: Array<Object>;
height?: number;
}
const props = withDefaults(defineProps<Props>(), {
height: 800,
});
const itemsPerParge = ref(25);
const search = ref("");
const sortBy = ref([{ key: "system", order: "asc" }]);
const expanded = ref([]);
function filterOnlyCapsText(value, query, item) {
if (value != null && query != null) {
if (typeof value === "string") {
return value.toString().toLowerCase().indexOf(query.toLowerCase()) !== -1;
}
if (typeof value == "object") {
if (value?.name) {
return (
value.name.toString().toLowerCase().indexOf(query.toLowerCase()) !==
-1
);
}
}
}
return false;
}
const { initPfam } = usePfamStore();
onMounted(() => {
initPfam();
})
</script>
<template>
<v-card>
<v-toolbar density="compact">
<v-toolbar-title>Defense Systems</v-toolbar-title>
<v-text-field v-model="search" density="compact" variant="underlined" prepend-inner-icon="mdi-magnify"
label="Search for defense systems" single-line hide-details class="mx-2" clearable></v-text-field>
</v-toolbar>
<v-data-table-virtual fixed-header :height="height" :items-per-page="itemsPerParge" v-model:sort-by="sortBy"
:headers="props.headers" density="compact" :custom-filter="filterOnlyCapsText" :items="props.systems"
:search="search" item-value="system.name">
<template #[`item.system`]="{ item }">
<v-chip variant="text" link :to="item?.system?.path ? `${item?.system?.path}` : null">{{
item?.system?.name ?? 'None'
}}</v-chip>
</template>
<template #[`item.article`]="{ item }">
<ArticleDoi v-if="item?.article" :doi="item.article.doi" :title="item.article?.title"
:abstract="item.article?.abstract" :divider="false" :enumerate="false" />
</template>
<template #[`item.PFAM`]="{ item }">
<pfam-chips v-if="item?.PFAM" :pfam-string="item.PFAM"></pfam-chips>
</template>
</v-data-table-virtual>
</v-card>
</template>
\ No newline at end of file
......@@ -10,7 +10,7 @@
<script setup lang="ts">
const slot = useSlots()
const el = ref(null)
const el: Ref<HTMLElement | null> = ref(null)
const rendered = ref(false)
async function render() {
......
......@@ -105,86 +105,43 @@ useHead({
const pdbeMolstarComponent = ref(null)
// const selectedPdb = ref("/wiki/avs/AVAST_I,AVAST_I__Avs1A,0,V-plddts_85.07081.pdb")
const selectedPdb: Ref<string | null> = ref(null)
const structureToDownload: Ref<string | null> = ref(null)
const selectedPaePath = computed(() => {
return selectedPdb.value ? `${selectedPdb.value.split(".").slice(0, -1).join('.')}.png` : null
})
// const paeData = ref([])
// watch(selectedPaePath, async (newPaePath) => {
// if (newPaePath !== null) {
// try {
// const data = await d3.tsv(newPaePath);
// if (data.length > 500) {
// paeError.value = `The PAE is too large to be displayed (${data.length} residus)`
// paeData.value = []
// }
// else if (data?.[0]?.[0] === undefined) {
// paeError.value = "The PAE cannot be downloaded"
// paeData.value = []
// }
// else {
// paeData.value = data
// paeError.value = null
// }
// }
// catch (error) {
// console.log(error)
// }
// } else {
// paeData.value = []
// }
// })
// const sanitizedPaeData = computed(() => {
// return paeData.value.reduce((acc, curr, index) => {
// const scoredResidue = index
// // let newAcc = [...acc]
// for (const [alignedResidue, value] of Object.entries(curr)) {
// // console.log(value)
// acc.push({ alignedResidue: parseInt(alignedResidue), scoredResidue: parseInt(scoredResidue), value: parseFloat(value) })
// // newAcc = [...newAcc, ...[{ alignedResidue: parseInt(alignedResidue), scoredResidue: parseInt(scoredResidue), value: parseFloat(value) }]]
// }
// return acc
// }, [])
// })
// const plotPaeOptions = computed(() => {
// return {
// width: 640,
// height: 640,
// color: { scheme: "Greens", legend: true, reverse: true, label: "Expected position error (Ångströms)" },
// y: { reverse: true },
// marks: [
// Plot.dot(sanitizedPaeData.value, { x: "scoredResidue", y: "alignedResidue", stroke: "value" })
// ]
// }
// })
watch(selectedPdb, (newSelectedPdb, prevSelectPdb) => {
viewPdb(newSelectedPdb)
structureToDownload.value = newSelectedPdb
})
onMounted(() => {
const urls = toValue(refinedDataUrls)
if (props.uniq && urls.length >= 1) {
structureToDownload.value = urls[0]
}
})
function viewPdb(pdbPath: string | null) {
if (pdbPath !== null) {
dialog.value = true
const format = toValue(pdbPath)?.split(".").slice(-1)[0]?.toLowerCase() ?? "pdb"
moleculeFormat.value = format
if (pdbeMolstarComponent.value?.viewerInstance) {
console.log(pdbeMolstarComponent.value)
const viewerInstance = pdbeMolstarComponent.value.viewerInstance
const customData = { url: pdbPath, format: format, binary: false }
viewerInstance.visual.update({ customData })
}
}
}
function setSelectedPdbToFirst() {
const urls = toValue(refinedDataUrls)
if (urls.length >= 1) {
......@@ -200,107 +157,116 @@ const moleculeFormat: Ref<string> = ref("pdb")
<template>
<span class="d-flex flex-wrap align-center justify-center">
<template v-if="uniq">
<v-row>
<v-btn size="x-small" variant="tonal" icon="md:visibility" @click="setSelectedPdbToFirst()"></v-btn>
<v-btn :disabled="!structureToDownload" size="x-small" variant="tonal" icon="md:download" class="ml-1"
:href="structureToDownload"></v-btn>
</v-row>
</template>
<v-row v-else>
<v-col>
<v-btn v-if="uniq" size="x-small" variant="tonal" icon="md:visibility" @click="setSelectedPdbToFirst()"></v-btn>
<v-select v-else v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto">
</v-select>
<span class="d-flex flex-wrap align-center justify-center">
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto">
</v-select>
</span>
</v-col>
<v-dialog v-model="dialog" transition="dialog-bottom-transition" fullscreen :scrim="false">
<v-card flat :rounded="false">
<v-toolbar>
<v-toolbar-title>Structures</v-toolbar-title>
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls"
hide-details="auto"></v-select>
<v-spacer></v-spacer>
<v-btn :disabled="!selectedPdb" icon="md:download" :href="selectedPdb"></v-btn>
<v-btn icon @click="closeStructure">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-row>
<v-col :cols="mobile ? 12 : 'auto'">
<v-sheet v-if="selectedPdb"
class="d-flex align-center justify-center flex-wrap text-center mx-auto px-4 my-3"
:height="computedHeight" :width="computedWidth" position="relative">
<pdbe-molstar ref="pdbeMolstarComponent" hide-controls="true" landscape="true"
:custom-data-url="selectedPdb" alphafold-view="true"
:custom-data-format="moleculeFormat"></pdbe-molstar>
</v-sheet>
</v-col>
<v-col :cols="mobile ? 12 : undefined">
<v-img :src="selectedPaePath"></v-img>
<!-- <PlotFigure v-if="sanitizedPaeData?.length > 0 && paeError === null" defer
</v-row>
<v-dialog v-model="dialog" transition="dialog-bottom-transition" fullscreen :scrim="false">
<v-card flat :rounded="false">
<v-toolbar>
<v-toolbar-title>Structures</v-toolbar-title>
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto"></v-select>
<v-spacer></v-spacer>
<v-btn :disabled="!selectedPdb" icon="md:download" :href="structureToDownload"></v-btn>
<v-btn icon @click="closeStructure">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-row>
<v-col :cols="mobile ? 12 : 'auto'">
<v-sheet v-if="selectedPdb"
class="d-flex align-center justify-center flex-wrap text-center mx-auto px-4 my-3"
:height="computedHeight" :width="computedWidth" position="relative">
<pdbe-molstar ref="pdbeMolstarComponent" landscape :custom-data-url="selectedPdb"
alphafold-view="true" :custom-data-format="moleculeFormat"></pdbe-molstar>
</v-sheet>
</v-col>
<v-col :cols="mobile ? 12 : undefined">
<v-img :src="selectedPaePath"></v-img>
<!-- <PlotFigure v-if="sanitizedPaeData?.length > 0 && paeError === null" defer
:options="plotPaeOptions"></PlotFigure>
<v-alert v-else type="warning" variant="tonal">{{ paeError }}</v-alert> -->
<v-card flat color="transparent">
<v-card-title>Model Confidence</v-card-title>
<v-card-text>
AlphaFold produces a per-residue model
confidence score (pLDDT) between 0 and 100. Some regions below 50 pLDDT may be
unstructured
in isolation.
</v-card-text>
<v-list>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(0, 83, 214);">
&nbsp;</div>
</template>
<v-list-item-title>
Very high (pLDDT > 90)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(101, 203, 243);">
&nbsp;</div>
</template>
<v-list-item-title>
High (90 > pLDDT > 70)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 219, 19);">
&nbsp;</div>
</template>
<v-list-item-title>
Low (70 > pLDDT > 50) </v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 125, 69);">
&nbsp;</div>
</template>
<v-list-item-title>
Very low (pLDDT &lt; 50) </v-list-item-title>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</span>
<v-card flat color="transparent">
<v-card-title>Model Confidence</v-card-title>
<v-card-text>
AlphaFold produces a per-residue model
confidence score (pLDDT) between 0 and 100. Some regions below 50 pLDDT may be
unstructured
in isolation.
</v-card-text>
<v-list>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(0, 83, 214);">
&nbsp;</div>
</template>
<v-list-item-title>
Very high (pLDDT > 90)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(101, 203, 243);">
&nbsp;</div>
</template>
<v-list-item-title>
High (90 > pLDDT > 70)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 219, 19);">
&nbsp;</div>
</template>
<v-list-item-title>
Low (70 > pLDDT > 50) </v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 125, 69);">
&nbsp;</div>
</template>
<v-list-item-title>
Very low (pLDDT &lt; 50) </v-list-item-title>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</template>
<style scoped>
<style>
.msp-plugin .msp-plugin-content {
color: black !important;
}
div.msp-plugin-content.msp-layout-expanded {
z-index: 99 !important
}
.legendColor {
height: 16px;
width: 16px;
......
......@@ -11,6 +11,8 @@ const itemValue = ref("id");
const { width } = useDisplay();
const scaleTransform: Ref<string[]> = ref([])
const facets = ref([
"replicon",
"type",
......@@ -80,10 +82,7 @@ onMounted(async () => {
async function getAllHits(params) {
if (params.index === 'refseq') {
console.log(params.index)
const { data, error } = await useAsyncMeiliSearch(params)
console.log(error.value)
console.log(data.value)
allHits.value = data.value
}
}
......@@ -207,7 +206,8 @@ const binPlotOptions = ref({
})
const binPlotDataOptions = computed(() => {
return allHits.value?.hits?.length > 0 ? {
const toValueAllHits = toValue(allHits)
return toValueAllHits?.hits?.length > 0 ? {
...binPlotOptions.value,
color: {
...binPlotOptions.value.color,
......@@ -215,7 +215,7 @@ const binPlotDataOptions = computed(() => {
},
// fy: { domain: groupSortDomain.value },
marks: [
Plot.cell(allHits.value?.hits ?? [], Plot.group({ fill: "count" }, { x: "type", y: selectedTaxoRank.value, tip: true, inset: 0.5, sort: { y: "fill" } })),
Plot.cell(toValueAllHits?.hits ?? [], Plot.group({ fill: "count" }, { x: "type", y: selectedTaxoRank.value, tip: true, inset: 0.5, sort: { y: "fill" } })),
]
} : null
......@@ -282,13 +282,7 @@ const scaleType = ref("linear")
<ServerDbTable title="RefSeq" db="refseq" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps" @refresh:search="getAllHits">
<template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
RefSeq
</v-toolbar-title><v-spacer></v-spacer>
</v-toolbar>
</template>
<template #[`item.accession_in_sys`]="{ item }">
<CollapsibleChips :items="namesToAccessionChips(item.accession_in_sys)">
</CollapsibleChips>
......
......@@ -2,6 +2,9 @@
import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure";
import type { SortItem } from "@/components/ServerDbTable.vue"
import { useNumericalFilter } from "@/composables/useNumericalfilter"
import { ServerDbTable } from "#components"
const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }])
const itemValue = ref("id");
......@@ -24,7 +27,16 @@ const headers: Ref<Object[]> = ref([
// { title: "Type", key: "type", removable: true },
])
const { search: msSearch, result: msResult } = useMeiliSearch('structure')
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 numericalFilters = computed(() => {
const listFilters = [plddtsFilter, iptmFilter, pdockqFilter].map(f => toValue(f)).filter(f => f !== undefined)
return listFilters.length > 0 ? listFilters.join(" AND ") : undefined
})
const defaultDataTableServerProps = ref({
showExpand: false
......@@ -41,6 +53,7 @@ const dataTableServerProps = computed(() => {
function namesToCollapsibleChips(names: string[], file: string | null = null) {
if (file === null) {
return names.filter((it) => it !== "").map(it => ({ title: it.split("__").pop() }))
......@@ -74,21 +87,46 @@ const plddtDistribution = computed(() => {
}
})
function remove(key) {
headers.value = headers.value.filter(header => header.key !== key)
}
</script>
<template>
<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>
:data-table-server-props="dataTableServerProps" :numerical-filters="numericalFilters">
<template #numerical-filters="{ search }">
<v-row>
<v-col cols="12" 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="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="plddtsReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
<v-col cols="12" 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="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="iptmReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
<!-- pdockqReset -->
<v-col cols="12" 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="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="pdockqReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
</v-row>
</template>
<template #[`item.proteins_in_the_prediction`]="{ item }">
......@@ -102,9 +140,9 @@ function remove(key) {
<MolstarPdbePlugin v-if="item?.pdb && item.pdb !== 'na'"
:data-urls="[`/${toSystemName(item.system)}/${pdbNameToCif(item.pdb)}`]" uniq>
</MolstarPdbePlugin>
<span v-else class="d-flex flex-wrap align-center justify-center">
<v-icon color="warning" icon="md:dangerous"></v-icon>
</span>
<!-- <v-icon v-else color="warning" icon="md:dangerous"></v-icon> -->
</template>
<template #[`item.completed`]="{ item }">
<v-icon v-if="item.completed" color="success" icon="md:check"></v-icon>
......
......@@ -3,7 +3,7 @@ import type { SortItem } from "@/components/ServerDbTable.vue"
import { ServerDbTable } from "#components"
const sortBy: Ref<SortItem[]> = ref([{ key: 'title', order: "asc" }])
const itemValue = ref("title");
const facets: Ref<string[]> = ref(["title", "Sensor", "Effector", "Activator", "PFAM.AC"])
const facets: Ref<string[]> = ref(["title", "Sensor", "Effector", "Activator", "PFAM.AC", "PFAM.DE"])
const headers: Ref<Object[]> = ref([
{ title: "System", key: "title", removable: false },
{ title: "Article", key: "doi", removable: false },
......@@ -30,16 +30,13 @@ const dataTableServerProps = computed(() => {
}
})
const columnsToDownload = ref(['title', 'doi', 'Sensor', 'Activator', 'Effector', 'PFAM', 'contributors',])
</script>
<template>
<ServerDbTable title="systems" db="systems" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps">
<ServerDbTable title="List Systems" db="systems" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps" :columns-to-download="columnsToDownload">
<template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
List Systems </v-toolbar-title><v-spacer></v-spacer>
</v-toolbar>
</template>
<template #[`item.title`]="{ item }">
<v-chip color="info" link :to="`/defense-systems/${item.title.toLowerCase()}`">{{
......
import { ref } from 'vue';
import Papa from 'papaparse';
import { saveAs } from "file-saver";
// import { saveAs } from "file-saver";
export function useCsvDownload(index: MaybeRef<string>, baseName: MaybeRef<string> = "df"
export function useCsvDownload(
rawData: MaybeRef<Record<string, any>>,
columns: MaybeRef<string[] | undefined> = undefined,
baseName: MaybeRef<string> = 'data'
) {
const pending = ref(false)
const { search: msSearch, result: msResult } = useMeiliSearch(toValue(index))
const filename = ref(`${toValue(baseName)}-data.csv`)
const downloadCsv = async (
query: MaybeRef<string>,
filter: MaybeRef<string>,
sortBy: MaybeRef<string[]>,
params: MaybeRef<Record<string, any>>,
) => {
filename.value = `${toValue(baseName)}-${toValue(filter)}.csv`
pending.value = true
try {
await msSearch(
toValue(query),
{
...toValue(params),
filter: toValue(filter),
sort: toValue(sortBy)
})
const csvContent = Papa.unparse(toValue(msResult).hits);
// console.log(csvContent)
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, `${toValue(filename)}`);
} finally {
pending.value = false
}
const filename = ref(`df-${toValue(baseName)}.csv`)
const data = ref()
const blob = ref()
if (toValue(rawData)?.hits?.length > 0) {
data.value = toValue(rawData).hits.map(row => {
let sanitizedRow = { ...row }
if (sanitizedRow?.PFAM?.length > 0) {
sanitizedRow = {
...sanitizedRow,
PFAM: sanitizedRow.PFAM.map(({ AC }) => AC).join(", ")
}
}
if (sanitizedRow?.contributors?.length > 0) {
sanitizedRow = {
...sanitizedRow,
contributors: sanitizedRow.contributors.join(", ")
}
}
return sanitizedRow
})
const csvContent = Papa.unparse(toValue(data), { columns: toValue(columns) });
blob.value = new Blob([csvContent], { type: "text/csv" });
var a = document.createElement("a");
a.href = URL.createObjectURL(blob.value);
a.download = filename.value;
a.click();
URL.revokeObjectURL(a.href);
}
// watch(msResult, (newRes) => {
// console.log("save file !!!!!!")
// })
return {
pending, downloadCsv,
};
return { data, filename }
}
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
......@@ -5,7 +5,7 @@ navigation:
icon: "mdi-database"
---
# Structures' prediction DB
# Structure's prediction DB
In the following tables are various structures that were generated by Alphafold for all monomers, hetero- and homo-dimers for a given system.
In the page for each system is the structure for the monomers and real structure when it exists.
......
......@@ -56,7 +56,9 @@ spec:
mountPath: /etc/nginx/conf.d/
- name: structure-data-pvc
mountPath: /structure-data
- name: website-pvc
mountPath: /website
{{- with .Values.nodeSelector }}
nodeSelector:
......@@ -79,3 +81,6 @@ spec:
- name: structure-data-pvc
persistentVolumeClaim:
claimName: structure-data-pvc
- name: website-pvc
persistentVolumeClaim:
claimName: website-pvc
......@@ -9,6 +9,6 @@ spec:
- ReadWriteMany
resources:
requests:
storage: 30Gi
storage: 35Gi
storageClassName: isilon
volumeMode: Filesystem
\ No newline at end of file
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: website-pvc
annotations:
"helm.sh/resource-policy": keep
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: isilon
volumeMode: Filesystem
\ No newline at end of file
......@@ -8,7 +8,6 @@
"@observablehq/plot": "^0.6.13",
"@pinia/nuxt": "^0.5.1",
"d3": "^7.8.5",
"file-saver": "^2.0.5",
"meilisearch": "^0.36.0",
"mermaid": "^10.6.1",
"papaparse": "^5.4.1",
......@@ -5704,10 +5703,6 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"license": "MIT"
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"dev": true,
......
......@@ -22,7 +22,6 @@
"@observablehq/plot": "^0.6.13",
"@pinia/nuxt": "^0.5.1",
"d3": "^7.8.5",
"file-saver": "^2.0.5",
"meilisearch": "^0.36.0",
"mermaid": "^10.6.1",
"papaparse": "^5.4.1",
......
......@@ -61,6 +61,7 @@ class StrucutreStatistics(BaseModel):
iptm_ptm: NaFloat = Field(..., alias="iptm+ptm")
pDockQ: Optional[NaFloat]
plddts: Optional[NaFloat]
Foldseek_name: Optional[str]
def update_refseq(
......@@ -193,7 +194,7 @@ def update_systems(
)
print(pagination_settings_task)
attr_task = index.update_filterable_attributes(
body=["title", "Sensor", "Activator", "Effector", "PFAM.AC"]
body=["title", "Sensor", "Activator", "Effector", "PFAM.AC", "PFAM.DE"]
)
params = {
"maxValuesPerFacet": 1000000,
......
#!/bin/bash
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 {} +