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

handle facet search

parent a8c8d105
No related branches found
No related tags found
1 merge request!34Server datatable
Pipeline #116016 passed with stages
in 7 minutes and 25 seconds
import { MeiliSearch } from 'meilisearch'
import { useRuntimeConfig, watchEffect, type MaybeRef, ref, toValue } from '#imports'
export function useFetchMsDocument(index: MaybeRef<string> = "", search: MaybeRef<string> = "", filter: MaybeRef<string> = null, limit: MaybeRef<number> = 1000) {
import type { FacetDistribution, Hits } from 'meilisearch';
export function useFetchMsDocument(
index: MaybeRef<string> = ref(""),
search: Ref<string> = ref(""),
filter: Ref<string> = ref(''),
limit: Ref<number> = ref(1000),
hitsPerPage: Ref<number> = ref(25),
page: Ref<number> = ref(1),
facets: Ref<string[]> = ref([])
) {
const runtimeConfig = useRuntimeConfig();
const client = new MeiliSearch({
......@@ -10,19 +18,35 @@ export function useFetchMsDocument(index: MaybeRef<string> = "", search: MaybeRe
})
const pending = ref(false)
const filterError = ref(null)
const hits = ref([])
const hits: Ref<Hits<Record<string, any>>> = ref([])
const totalHits = ref(0)
const totalPages = ref(0)
const facetDistribution: Ref<FacetDistribution | undefined> = ref({})
// reset page when filter and search change
watch(filter, () => {
page.value = 1
})
watch(search, () => {
page.value = 1
})
watchEffect(async () => {
try {
pending.value = true
const res = await client.index(toValue(index)).search(toValue(search), {
limit: toValue(limit),
filter: toValue(filter)
filter: toValue(filter),
hitsPerPage: toValue(hitsPerPage),
page: toValue(page),
facets: toValue(facets)
})
filterError.value = null
const { hits: resHits, estimatedTotalHits } = res
totalHits.value = estimatedTotalHits
const { hits: resHits, totalHits: resTotalHits, totalPages: resTotalPages, facetDistribution: facetD } = res
totalHits.value = resTotalHits
hits.value = resHits
totalPages.value = resTotalPages
facetDistribution.value = facetD
} catch ({ code, message }) {
if (code === 'invalid_search_filter') {
filterError.value = message
......@@ -32,6 +56,6 @@ export function useFetchMsDocument(index: MaybeRef<string> = "", search: MaybeRe
}
})
return { hits, totalHits, pending, filterError }
return { hits, totalHits, pending, filterError, totalPages, facetDistribution }
}
......@@ -3,9 +3,10 @@ import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure";
import { useDisplay } from "vuetify";
import JsonCSV from 'vue-json-csv';
import { useFacetsStore, type Facets } from '~~/stores/facets'
const runtimeConfig = useRuntimeConfig();
const facetStore = useFacetsStore()
const { width, height } = useDisplay();
const minTableHeight = ref(400)
const computedTableHeight = computed(() => {
......@@ -30,10 +31,12 @@ const plotHeight = computed(() => {
});
// const filterError = ref(null)
const search: Ref<string> = ref("");
const filter = ref(null)
const limit = ref(500000)
const filter: Ref<string> = ref('')
const hitsPerPage: Ref<number> = ref(25)
const limit = ref(1000)
const itemValue = ref("id");
const facets = ref(["type"])
const page = ref(1)
const prependHeaders = ref([
{ title: "Replicon", key: "replicon" },
{
......@@ -54,11 +57,40 @@ const appendHeaders = ref([
}
])
function capitalize([first, ...rest]) {
return first.toUpperCase() + rest.join('').toLowerCase();
}
const computedFacets = computed(() => {
return [...facets.value, selectedTaxoRank.value]
})
const { hits: items, pending, totalHits: itemsLength, filterError, facetDistribution } = useFetchMsDocument("refseq", search, filter, limit, hitsPerPage, page, computedFacets)
watch(facetDistribution, (facetDistri) => {
facetStore.setFacets({ facetDistribution: facetDistri, facetStat: undefined })
})
const computedSystemDistribution = computed(() => {
if (facetDistribution.value?.type) {
return Object.entries(facetDistribution.value.type).map(([key, value]) => {
return { type: key, count: value }
}).sort()
} else { return [] }
})
const computedTaxonomyDistribution = computed(() => {
if (facetDistribution.value?.[selectedTaxoRank.value]) {
return Object.entries(facetDistribution.value[selectedTaxoRank.value]).map(([key, value]) => {
return { [selectedTaxoRank.value]: key, count: value }
}).sort()
} else { return [] }
})
const { hits: refseqData, pending, totalHits, filterError } = useFetchMsDocument("refseq", search, filter, limit)
const computedHeaders = computed(() => {
return [...prependHeaders.value, ...availableTaxo.value.map(taxo => {
......@@ -71,100 +103,84 @@ const computedHeaders = computed(() => {
function itemToFilter(item, key) {
const value = item[key]
const filterToAdd = /\s/g.test(value) ? `${key}="${value}"` : `${key}=${value}`
return filter.value === null ? filterToAdd : `${filter.value} AND ${filterToAdd}`
return filter.value === '' ? filterToAdd : `${filter.value} AND ${filterToAdd}`
}
const itemFilterKeys = computed(() => {
return [...availableTaxo.value, 'type', 'subtype']
})
const computedDistriSystemOptions = computed(() => {
const groupYOption = facetDistriSystem.value
? {
fx: "type",
x: selectedTaxoRank.value,
// fill: selectedTaxoRank.value,
tip: true,
sort: { y: "-x" },
}
: {
x: "type",
tip: true,
fill: "#6750a4",
// fill: selectedTaxoRank.value,
sort: { x: "-y" },
};
const defaultBarPlotOptions = computed(() => {
return {
marginLeft: 30,
marginBottom: 120,
x: { label: null, tickRotate: 70 },
y: { grid: true },
y: { nice: true, grid: true },
color: { legend: true },
width: computedWidth.value,
height: plotHeight.value,
}
})
const computedDistriSystemOptions = computed(() => {
return {
...defaultBarPlotOptions.value,
marginBottom: 120,
marks: [
// Plot.frame(),
Plot.barY(
unref(refseqData.value),
Plot.groupX(
{ y: "count" },
groupYOption
)
toValue(computedSystemDistribution),
{
y: "count", x: 'type', tip: true,
fill: "#6750a4",
sort: { x: "-y" },
},
),
],
};
});
const computedDistriTaxoOptions = computed(() => {
const groupYOption = {
x: selectedTaxoRank.value,
// fx: selectedTaxoRank.value,
tip: true,
// fill: "type",
fill: "#6750a4",
// offset: "normalize",
sort: { x: "-y" },
};
return {
// marginLeft: 110,
marginBottom: 100,
grid: true,
x: { label: null, tickRotate: 70 },
// x: { label: facet.value ? "Count" : null, tickRotate: 90 },
// y: { nice: true },
color: { legend: true },
width: computedWidth.value,
height: plotHeight.value,
...defaultBarPlotOptions.value,
marginBottom: 200,
marks: [
// Plot.frame(),
Plot.barY(
unref(refseqData.value),
Plot.groupX({ y: "count" }, groupYOption)
toValue(computedTaxonomyDistribution),
{
y: "count",
x: selectedTaxoRank.value,
tip: true,
fill: "#6750a4",
sort: { x: "-y" },
}
),
],
};
});
const datatable = ref(null)
// const datatable = ref(null)
const hasToGenerateDownload = ref(false)
let itemsToDownload = ref()
watch(hasToGenerateDownload, (val) => {
console.log(val)
if (val === true) {
const { hits: items, pending, totalHits: itemsLength, filterError, facetDistribution } = useFetchMsDocument("refseq", search, filter, limit, hitsPerPage, page, computedFacets)
itemsToDownload.value = items.value
}
})
</script>
<template>
<v-card flat>
<v-toolbar color="primary" density="compact">
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>RefSeq Entries ({{ totalHits }})
<v-toolbar-title>RefSeq Entries ({{ itemsLength }})
</v-toolbar-title>
<JsonCSV :data="refseqData" name="refseq-defense-system.csv">
<v-btn icon>
<JsonCSV :data="itemsToDownload" name="refseq-defenes-system.csv">
<v-btn icon @click="hasToGenerateDownload = true">
<v-icon icon="md:download"></v-icon>
<v-tooltip activator="parent" location="bottom">Download {{ refseqData.length }} entries</v-tooltip>
<v-tooltip activator="parent" location="bottom">Download {{ itemsLength }} entries</v-tooltip>
</v-btn>
</JsonCSV>
</v-toolbar>
......@@ -181,32 +197,30 @@ const datatable = ref(null)
<v-col cols="auto">
<v-text-field v-model="filter" prepend-inner-icon="mdi-magnify" label="Filter" hide-details="auto" class="mx-2"
clearable :error-messages="filterError"></v-text-field></v-col>
<v-data-table-virtual ref="datatable" fixed-header :loading="pending" :headers="computedHeaders" :items="refseqData"
:item-value="itemValue" density="compact" :height="computedTableHeight" class="elevation-1 mt-2">
<v-data-table-server v-model:page="page" v-model:items-per-page="hitsPerPage" fixed-header :loading="pending"
:headers="computedHeaders" :items="items" :items-length="itemsLength" :item-value="itemValue" density="compact"
:height="computedTableHeight" class="elevation-1 mt-2">
<template #[`item.${key}`]="{ item }" v-for="key in itemFilterKeys" :key="key">
<v-chip @click="filter = itemToFilter(item, key)">{{ item[key] }}</v-chip>
</template>
<template #[`item.accession_in_sys`]="{ item }">
<accession-chips :accessions="item.accession_in_sys" baseUrl="http://toto.pasteur.cloud"></accession-chips>
</template>
</v-data-table-virtual>
</v-data-table-server>
</v-card>
<v-card flat class="my-3" :loading="pending">
<v-card-title> Systems Distribution</v-card-title>
<!-- <v-toolbar density="compact"><v-toolbar-title> Distribution Systems</v-toolbar-title></v-toolbar> -->
<v-card-text>
<PlotFigure :options="unref(computedDistriSystemOptions)" defer></PlotFigure>
</v-card-text>
</v-card>
<v-card flat :loading="pending">
<v-card-title> Taxonomic Distribution</v-card-title>
<!-- <v-toolbar density="compact"><v-toolbar-title> Distribution Taxonomy</v-toolbar-title></v-toolbar> -->
<v-card-text>
<v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
label="Select taxonomic rank"></v-select>
<!-- <v-switch v-model="facet" label="Facet" color="primary"></v-switch> -->
<PlotFigure defer :options="unref(computedDistriTaxoOptions)"></PlotFigure>
</v-card-text>
</v-card>
......
import { defineStore } from 'pinia'
import type { FacetDistribution, FacetStat } from 'meilisearch';
export interface Facets {
facetDistribution: FacetDistribution | undefined;
facetStat: FacetStat | undefined;
}
export const useFacetsStore = defineStore('facets', () => {
const facets: Ref<Facets> = ref({ facetDistribution: undefined, facetStat: undefined })
function setFacets(newFacets: Facets) {
facets.value = newFacets
}
return { facets, setFacets }
})
\ 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