Commit b9df62f1 authored by Kenzo-Hugo Hillion's avatar Kenzo-Hugo Hillion
Browse files

Merge branch '128-limits-page-number' into 'dev'

Limits page number and display loading when changing page

Closes #127 and #128

See merge request !52
parents 28675317 28fd46b0
Pipeline #31623 passed with stages
in 3 minutes and 4 seconds
......@@ -12,7 +12,9 @@ logger = logging.getLogger(__name__)
class ComputeStatistics:
GENE_SOURCES = ['all', 'igc', 'virgo']
def __init__(self, gene_source):
self.gene_source = gene_source
def _save_to_db(self, payload):
try:
......@@ -39,13 +41,13 @@ class ComputeCounts(ComputeStatistics):
]
FUNCTION_SOURCES = ['kegg', 'eggnog']
def compute_count(self, method, gene_source, filters=None, **kwargs):
def compute_count(self, method, filters=None, **kwargs):
if filters is None:
filters = {}
gene_stats = GeneStatistics(filters=filters)
print_kwargs = '-'.join([str(k) + '-' + str(v) for k, v in kwargs.items() if v])
print_filters = '-'.join([str(k) + '-' + str(v) for k, v in filters.items() if v])
stats_id = slugify(f"GeneStatistics({gene_source}).{method}({print_kwargs})")
stats_id = slugify(f"GeneStatistics({self.gene_source}).{method}({print_kwargs})")
logger.info(
"Call GeneStatistics(%s).%s(%s) and saving under id <%s>",
print_filters, method, print_kwargs, stats_id
......@@ -59,15 +61,14 @@ class ComputeCounts(ComputeStatistics):
self._save_to_db(payload)
def all(self):
for gene_source in self.GENE_SOURCES:
if gene_source == 'all':
filters = {}
else:
filters = {'source': gene_source}
for method in self.METHODS:
self.compute_count(method, gene_source, filters=filters)
for source in self.FUNCTION_SOURCES:
self.compute_count('count_has_function', gene_source, filters=filters, source=source)
if self.gene_source == 'all':
filters = {}
else:
filters = {'source': self.gene_source}
for method in self.METHODS:
self.compute_count(method, filters=filters)
for source in self.FUNCTION_SOURCES:
self.compute_count('count_has_function', filters=filters, source=source)
class ComputeGeneLength(ComputeStatistics):
......@@ -87,13 +88,13 @@ class ComputeGeneLength(ComputeStatistics):
},
}
def _compute_gene_length(self, filters, category, gene_source):
def _compute_gene_length(self, filters, category):
gene_stats = GeneLengthDistribution(
window_size=self.MIN_WINDOW_SIZE, stop_at=self.MAX_STOP_AT, filters=filters
)
for window_size in self.WINDOW_SIZES:
for stop_at in self.STOP_ATS:
stats_id = slugify(f"GeneStatistics({gene_source}).gene-length-{window_size}-{stop_at}-{category}")
stats_id = slugify(f"GeneStatistics({self.gene_source}).gene-length-{window_size}-{stop_at}-{category}")
logger.info(
"Call GeneStatistics.gene_length(%s, %s) for %s and saving under id <%s>",
window_size, stop_at, category, stats_id)
......@@ -104,15 +105,14 @@ class ComputeGeneLength(ComputeStatistics):
self._save_to_db(payload)
def all(self):
for gene_source in self.GENE_SOURCES:
if gene_source == 'all':
filters = {}
else:
filters = {'source': gene_source}
for category, cat_filters in self.CATEGORIES.items():
if cat_filters is not None:
filters.update(**cat_filters)
self._compute_gene_length(filters, category, gene_source)
if self.gene_source == 'all':
filters = {}
else:
filters = {'source': self.gene_source}
for category, cat_filters in self.CATEGORIES.items():
if cat_filters is not None:
filters.update(**cat_filters)
self._compute_gene_length(filters, category)
class ComputeTaxonomyRepartition(ComputeStatistics):
......@@ -121,23 +121,22 @@ class ComputeTaxonomyRepartition(ComputeStatistics):
]
def all(self):
for gene_source in self.GENE_SOURCES:
if gene_source == 'all':
filters = {}
else:
filters = {'source': gene_source}
gene_stats = GeneStatistics(filters=filters)
for level in self.ALL_LEVEL:
stats_id = slugify(f"GeneStatistics({gene_source}).taxonomy_repartition({level})")
logger.info(
"Call GeneStatistics.taxonomy_repartition(%s) and saving under id <%s>",
level, stats_id
)
payload = {
'stats_id': stats_id,
'body': gene_stats.taxonomy_repartition(level=level)
}
self._save_to_db(payload)
if self.gene_source == 'all':
filters = {}
else:
filters = {'source': self.gene_source}
gene_stats = GeneStatistics(filters=filters)
for level in self.ALL_LEVEL:
stats_id = slugify(f"GeneStatistics({self.gene_source}).taxonomy_repartition({level})")
logger.info(
"Call GeneStatistics.taxonomy_repartition(%s) and saving under id <%s>",
level, stats_id
)
payload = {
'stats_id': stats_id,
'body': gene_stats.taxonomy_repartition(level=level)
}
self._save_to_db(payload)
class ComputeTaxonomyPresence(ComputeStatistics):
......@@ -146,31 +145,32 @@ class ComputeTaxonomyPresence(ComputeStatistics):
]
def all(self):
for gene_source in self.GENE_SOURCES:
if gene_source == 'all':
filters = {}
else:
filters = {'source': gene_source}
gene_stats = GeneStatistics(filters=filters)
for level in self.ALL_LEVEL:
stats_id = slugify(f"GeneStatistics({gene_source}).present_taxonomy({level})")
logger.info(
"Call GeneStatistics.present_taxonomy(%s) and saving under id <%s>",
level, stats_id
)
payload = {
'stats_id': stats_id,
'body': gene_stats.present_taxonomy(level=level)
}
self._save_to_db(payload)
if self.gene_source == 'all':
filters = {}
else:
filters = {'source': self.gene_source}
gene_stats = GeneStatistics(filters=filters)
for level in self.ALL_LEVEL:
stats_id = slugify(f"GeneStatistics({self.gene_source}).present_taxonomy({level})")
logger.info(
"Call GeneStatistics.present_taxonomy(%s) and saving under id <%s>",
level, stats_id
)
payload = {
'stats_id': stats_id,
'body': gene_stats.present_taxonomy(level=level)
}
self._save_to_db(payload)
class Command(BaseCommand):
help = "Compute gene catalog statistics."
STEP_CHOICES = ['clean', 'counts', 'gene-length', 'taxonomy_repartition', 'taxonomy_presence']
SOURCE_CHOICES = ['all', 'virgo', 'igc']
def add_arguments(self, parser):
parser.add_argument('--only', help=f'Run only one step (choices: {self.STEP_CHOICES}).')
parser.add_argument('--source', help=f'Run only one step (choices: {self.SOURCE_CHOICES}).')
def set_logger_level(self, verbosity):
if verbosity > 2:
......@@ -178,22 +178,38 @@ class Command(BaseCommand):
elif verbosity > 1:
logger.setLevel(logging.INFO)
def handle(self, *args, **options):
self.set_logger_level(int(options['verbosity']))
only_step = str(options['only'])
if only_step is not None:
if only_step not in self.STEP_CHOICES:
def _get_and_validate_only_step(self, only_step_str):
if only_step_str is not None:
if only_step_str not in self.STEP_CHOICES:
logger.warning(
"Choice '%s' is not a valid choice. Please choose among %s",
only_step, self.STEP_CHOICES
only_step_str, self.STEP_CHOICES
)
if only_step is None or only_step == "clean":
ComputeStatistics().clean_db()
if only_step is None or only_step == "counts":
ComputeCounts().all()
if only_step is None or only_step == "gene-length":
ComputeGeneLength().all()
if only_step is None or only_step == "taxonomy_repartition":
ComputeTaxonomyRepartition().all()
if only_step is None or only_step == "taxonomy_presence":
ComputeTaxonomyPresence().all()
return only_step_str
def _get_and_validate_source(self, source_str):
if source_str is not None:
if source_str not in self.SOURCE_CHOICES:
logger.warning(
"Choice '%s' is not a valid choice. Please choose among %s",
source_str, self.SOURCE_CHOICES
)
return []
return [source_str]
return self.SOURCE_CHOICES
def handle(self, *args, **options):
self.set_logger_level(int(options['verbosity']))
only_step = self._get_and_validate_only_step(options['only'])
gene_sources = self._get_and_validate_source(options['source'])
for gene_source in gene_sources:
if only_step is None or only_step == "clean":
ComputeStatistics(gene_source).clean_db()
if only_step is None or only_step == "counts":
ComputeCounts(gene_source).all()
if only_step is None or only_step == "gene-length":
ComputeGeneLength(gene_source).all()
if only_step is None or only_step == "taxonomy_repartition":
ComputeTaxonomyRepartition(gene_source).all()
if only_step is None or only_step == "taxonomy_presence":
ComputeTaxonomyPresence(gene_source).all()
import Vue from 'vue';
import Router from 'vue-router';
import About from '@/views/about/about.vue';
import Catalogstats from '@/views/stats/stats.vue';
import Compare from '@/views/compare/compare.vue'
import GeneDetail from '@/views/GeneDetail.vue';
import Genes from '@/views/genes/genes.vue';
import Home from '@/views/Home.vue';
import Home from '@/views/home/home.vue';
Vue.use(Router);
......@@ -41,10 +42,7 @@ export default new Router({
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
component: About,
},
],
});
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<template>
<h1>Welcome!</h1>
</template>
<script>
export default {
};
</script>
<div class="about">
<h1>This is an about page</h1>
</div>
\ No newline at end of file
<template src="./about.html" lang="html"></template>
<script src="./about.js" lang="js"></script>
\ No newline at end of file
......@@ -189,58 +189,65 @@
</v-expansion-panel-content>
</v-expansion-panel>
<!-- Table -->
<v-list v-if="!loadTable">
<v-data-table
:rows-per-page-items="rowsPerPageItems"
:pagination.sync="pagination"
:headers="headers"
:items="genes"
hide-actions
>
<template v-slot:items="props">
<td>
<router-link :to="/gene-detail/ + props.item.gene_id">{{ props.item.gene_id }}</router-link>
</td>
<td>{{ props.item.name }}</td>
<td class="text-xs">{{ props.item.length }}</td>
<td class="text-xs">
{{ props.item.taxonomy }}
<v-btn :href="props.item.tax_url" icon flat small target="_blank" class="taxonomy--text mt-1 ml-0" v-if="props.item.taxonomy">
<v-icon small>
open_in_new
</v-icon>
</v-btn>
</td>
<td class="text-xs">
{{ props.item.kegg }}
<v-btn :href="props.item.kegg_url" icon flat small target="_blank" class="kegg--text mt-1 ml-0" v-if="props.item.kegg">
<v-icon small>
open_in_new
</v-icon>
</v-btn>
</td>
<td class="text-xs">{{ props.item.eggnog }}</td>
<td class="text-xs">{{ props.item.source }}</td>
</template>
</v-data-table>
</v-list>
<!-- Loading bar -->
<v-card class="text-xs-center" v-if="!requestDone">
<v-progress-circular
indeterminate
color="secondary"
class="ma-2"
></v-progress-circular>
<v-progress-linear
indeterminate
height="4"
color="secondary"
></v-progress-linear>
</v-card>
<v-list v-else>
<v-data-table
:rows-per-page-items="rowsPerPageItems"
:pagination.sync="pagination"
:headers="headers"
:items="genes"
hide-actions
>
<template v-slot:items="props">
<td>
<router-link :to="/gene-detail/ + props.item.gene_id">{{ props.item.gene_id }}</router-link>
</td>
<td>{{ props.item.name }}</td>
<td class="text-xs">{{ props.item.length }}</td>
<td class="text-xs">
{{ props.item.taxonomy }}
<v-btn :href="props.item.tax_url" icon flat small target="_blank" class="taxonomy--text mt-1 ml-0" v-if="props.item.taxonomy">
<v-icon small>
open_in_new
</v-icon>
</v-btn>
</td>
<td class="text-xs">
{{ props.item.kegg }}
<v-btn :href="props.item.kegg_url" icon flat small target="_blank" class="kegg--text mt-1 ml-0" v-if="props.item.kegg">
<v-icon small>
open_in_new
</v-icon>
</v-btn>
</td>
<td class="text-xs">{{ props.item.eggnog }}</td>
<td class="text-xs">{{ props.item.source }}</td>
</template>
</v-data-table>
</v-list>
<v-card flat>
<div class="text-xs-center" v-if="count">
<v-pagination
v-model="pageNumber"
:length="pageTotalNumber"
:total-visible="7"
@input="getGenes"
></v-pagination>
</div>
<v-subheader>{{count}} genes found</v-subheader>
<div class="text-xs-center" v-if="count">
<v-pagination
v-model="pageNumber"
:length="pageTotalNumber"
:total-visible="7"
@input="getGenes"
></v-pagination>
</div>
<v-subheader>
{{count}} genes found
<v-spacer></v-spacer>
<span v-if="tooManyPages" class="font-italic font-weight-light">
Too many pages to be displayed (limited to {{maxNumberPages}}). You may want to apply filters to reduce pages number.
</span>
</v-subheader>
</v-card>
</v-flex>
</v-layout>
......
......@@ -12,6 +12,8 @@ export default {
rowsPerPage: 20,
},
pageNumber: 1,
maxNumberPages: 50000,
tooManyPages: false,
// Filters
// - Gene info
geneSource: 'all',
......@@ -24,6 +26,8 @@ export default {
taxChoice: null,
// - Function
functionID: null,
// Information about request
loadTable: true,
requestDone: false,
};
},
......@@ -91,10 +95,15 @@ export default {
return Object.keys(this.taxItems).sort();
},
pageTotalNumber() {
var numberPages = Math.floor(this.count/this.pagination.rowsPerPage);
if (numberPages > this.maxNumberPages) {
this.tooManyPages = true;
return this.maxNumberPages;
}
if ((this.count % this.pagination.rowsPerPage) > 0) {
return Math.floor(this.count/this.pagination.rowsPerPage) + 1;
return numberPages + 1;
}
return Math.floor(this.count/this.pagination.rowsPerPage);
return numberPages;
}
},
mounted() {
......@@ -112,6 +121,7 @@ export default {
return taxLevelsFormatted;
},
getGenes() {
this.requestDone = false;
axios.get('/api/catalog/v1/genes', {
params: this.qParams,
headers: {
......@@ -142,6 +152,7 @@ export default {
this.genes = genes;
this.count = response.data.count;
this.requestDone = true;
this.loadTable = false;
})
.catch((error) => {
console.error(error);
......@@ -187,7 +198,8 @@ export default {
this.getTaxoPresence();
},
runNewSearch() {
this.requestDone = false;
this.loadTable = true;
this.tooManyPages = false;
this.pageNumber = 1;
this.getGenes();
},
......
<div class="home">
</div>
\ No newline at end of file
export default {
};
\ No newline at end of file
<template src="./home.html" lang="html"></template>
<script src="./home.js" lang="js"></script>
\ No newline at end of file
Supports Markdown
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