From 7e9f161046c993fb2a773fdec46eebf39edf9c28 Mon Sep 17 00:00:00 2001
From: Remi  PLANEL <rplanel@pasteur.fr>
Date: Thu, 21 Dec 2023 12:54:52 +0100
Subject: [PATCH] Can download data server

---
 components/ServerDbTable.vue                  | 69 ++++++++++---------
 components/content/RefseqDb.vue               | 14 ++--
 components/content/StructureDb.vue            |  4 +-
 components/content/SystemDb.vue               | 13 ++--
 composables/useCsvDownload.ts                 | 43 +++++++++---
 content/5.structure.md                        |  2 +-
 .../df_wiki_cli/meilisearch/__init__.py       |  2 +-
 7 files changed, 84 insertions(+), 63 deletions(-)

diff --git a/components/ServerDbTable.vue b/components/ServerDbTable.vue
index cb80b6d8..b6acb9c4 100644
--- a/components/ServerDbTable.vue
+++ b/components/ServerDbTable.vue
@@ -18,6 +18,7 @@ export interface Props {
     sortBy?: SortItem[]
     facets: MaybeRef<string[]>
     dataTableServerProps: Record<string, any>
+    columnsToDownload: MaybeRef<string[]>
 }
 
 export interface FilterItem {
@@ -324,44 +325,20 @@ const { pending: pendingDownloadData, downloadCsv } = useCsvDownload(props.db)
 // })
 
 
-
+function downloadData() {
+    downloadCsv(
+        props.db, 
+        toValue(search), { ...toValue(notPaginatedParams), filter: toValue(computedFilter), sort: toValue(msSortBy) },
+        props.columnsToDownload
+        
+        )
+}
 </script>
 <template>
     <v-card flat color="transparent">
         <v-card-text>
         </v-card-text>
         <v-card-text>
-            <v-row>
-                <v-col :cols="mobile ? 12 : 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 :cols="mobile ? 12 : 6">
-                    <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"
-                        @click:appendInner="searchOrFilter" @click:clear="clearFilterOrSearch"
-                        @update:modelValue="() => clearSearch()">
-                        <template #chip="{ props, item, index }">
-                            <v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
-                                @click:close="item.props.type === deleteOneFilter(index)"></v-chip>
-                        </template>
-                        <template #item="{ props, item }">
-                            <v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
-                                :title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value">
-
-                            </v-list-item>
-                        </template>
-                    </v-autocomplete>
-                </v-col>
-                <v-col :cols="mobile ? 'auto' : 1">
-                    <v-badge :content="totalHits" color="primary">
-                        <v-btn icon="md:download"
-                            @click="downloadCsv(props.db, toValue(search), { ...notPaginatedParams.value, filter: toValue(computedFilter), sort: msSortBy.value })">
-                        </v-btn>
-                    </v-badge>
-                </v-col>
-            </v-row>
             <v-row v-if="props.db === 'structure'">
                 <v-col>
 
@@ -386,7 +363,35 @@ const { pending: pendingDownloadData, downloadCsv } = useCsvDownload(props.db)
             :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" 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"
+                        @click:appendInner="searchOrFilter" @click:clear="clearFilterOrSearch"
+                        @update:modelValue="() => clearSearch()">
+                        <template #chip="{ props, item, index }">
+                            <v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
+                                @click:close="item.props.type === deleteOneFilter(index)"></v-chip>
+                        </template>
+                        <template #item="{ props, item }">
+                            <v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
+                                :title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value">
 
+                            </v-list-item>
+                        </template>
+                    </v-autocomplete>
+
+                </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>
diff --git a/components/content/RefseqDb.vue b/components/content/RefseqDb.vue
index fc3fa610..38260b1f 100644
--- a/components/content/RefseqDb.vue
+++ b/components/content/RefseqDb.vue
@@ -206,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,
@@ -214,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
@@ -280,13 +281,8 @@ const scaleType = ref("linear")
             </v-expansion-panels>
 
             <ServerDbTable title="RefSeq" db="refseq" :sortBy="sortBy" :facets="facets"
-                :data-table-server-props="dataTableServerProps">
-                <template #top>
-                    <v-toolbar><v-toolbar-title class="text-capitalize">
-                            RefSeq
-                        </v-toolbar-title><v-spacer></v-spacer>
-                    </v-toolbar>
-                </template>
+                :data-table-server-props="dataTableServerProps" @refresh:search="getAllHits">
+
                 <template #[`item.accession_in_sys`]="{ item }">
                     <CollapsibleChips :items="namesToAccessionChips(item.accession_in_sys)">
                     </CollapsibleChips>
diff --git a/components/content/StructureDb.vue b/components/content/StructureDb.vue
index 6fc5a9c8..a02fc2c5 100644
--- a/components/content/StructureDb.vue
+++ b/components/content/StructureDb.vue
@@ -76,13 +76,13 @@ function remove(key) {
     <ServerDbTable title="Predicted Structures" db="structure" :sortBy="sortBy" :facets="facets"
         :data-table-server-props="dataTableServerProps">
 
-        <template #top>
+        <!-- <template #top>
             <v-toolbar><v-toolbar-title class="text-capitalize">
                     Predicted Structures
                 </v-toolbar-title><v-spacer></v-spacer>
 
             </v-toolbar>
-        </template>
+        </template> -->
 
         <template #[`item.proteins_in_the_prediction`]="{ item }">
             <CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.fasta_file)">
diff --git a/components/content/SystemDb.vue b/components/content/SystemDb.vue
index 72d1642f..65e3c152 100644
--- a/components/content/SystemDb.vue
+++ b/components/content/SystemDb.vue
@@ -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()}`">{{
diff --git a/composables/useCsvDownload.ts b/composables/useCsvDownload.ts
index 88589b8f..3ce2d7cd 100644
--- a/composables/useCsvDownload.ts
+++ b/composables/useCsvDownload.ts
@@ -1,6 +1,7 @@
 import { ref } from 'vue';
 import Papa from 'papaparse';
 import { saveAs } from "file-saver";
+import { column } from '@observablehq/plot';
 
 export function useCsvDownload(baseName: MaybeRef<string> = "df"
 ) {
@@ -12,33 +13,55 @@ export function useCsvDownload(baseName: MaybeRef<string> = "df"
     index: MaybeRef<string>,
     query: MaybeRef<string>,
     params: MaybeRef<Record<string, any>>,
+    columns: MaybeRef<string[] | undefined> = undefined
   ) => {
 
 
-    filename.value = `${toValue(baseName)}-${toValue(params).filter}.csv`
+    const toValueParams = toValue(params)
+    const filterName = toValueParams?.filter ? toValueParams.filter.replaceAll('\"', "") : ''
+    filename.value = `${toValue(baseName)}-${filterName}.csv`
     pending.value = true
+
     try {
       const { data, error } = await useAsyncMeiliSearch({
         index: toValue(index),
-        params: toValue(params),
+        params: toValueParams,
         query: toValue(query)
       })
 
-      const csvContent = Papa.unparse(toValue(data).hits);
-      // console.log(csvContent)
-      const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
-      saveAs(blob, `${toValue(filename)}`);
+      if (toValue(data)?.hits?.length > 0) {
+        const sanitizedData = toValue(data).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
+
+        })
+
+
+        console.log(sanitizedData)
+        const csvContent = Papa.unparse(sanitizedData, { columns: toValue(columns) });
+        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+        saveAs(blob, `${toValue(filename)}`);
+      }
     } finally {
       pending.value = false
     }
 
 
   }
-  // watch(msResult, (newRes) => {
-  //   console.log("save file !!!!!!")
-
 
-  // })
 
   return {
     pending, downloadCsv,
diff --git a/content/5.structure.md b/content/5.structure.md
index 923ce4a9..a21e7e49 100644
--- a/content/5.structure.md
+++ b/content/5.structure.md
@@ -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.
diff --git a/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py b/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
index b3ec461a..eb98e990 100644
--- a/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
+++ b/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
@@ -193,7 +193,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,
-- 
GitLab