From 67bb7af04f8aa2b740e4e002faf11ebab8c99fb5 Mon Sep 17 00:00:00 2001 From: vikasg11 Date: Fri, 15 Nov 2024 10:12:11 +0000 Subject: [PATCH] gene apis enhanced to support sorting --- .../organisms/GeneSearch/GeneResultsTable.tsx | 72 ++++++++++----- .../organisms/GeneSearch/GeneSearchForm.tsx | 31 +++---- .../src/components/pages/GeneViewerPage.tsx | 8 +- .../src/components/pages/HomePage.tsx | 8 +- dataportal-app/src/services/geneService.ts | 5 +- dataportal_api/dataportal/api.py | 3 + .../dataportal/services/gene_service.py | 92 ++++++++----------- 7 files changed, 122 insertions(+), 97 deletions(-) diff --git a/dataportal-app/src/components/organisms/GeneSearch/GeneResultsTable.tsx b/dataportal-app/src/components/organisms/GeneSearch/GeneResultsTable.tsx index fd8b8a3..87afc72 100644 --- a/dataportal-app/src/components/organisms/GeneSearch/GeneResultsTable.tsx +++ b/dataportal-app/src/components/organisms/GeneSearch/GeneResultsTable.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useState } from 'react'; import styles from './GeneResultsTable.module.scss'; -import {createViewState} from '@jbrowse/react-app'; +import { createViewState } from '@jbrowse/react-app'; type ViewModel = ReturnType; @@ -11,7 +11,7 @@ interface LinkData { interface GeneResultsTableProps { results: any[]; - onSortClick: (sortField: string) => void; + onSortClick: (sortField: string, sortOrder: 'asc' | 'desc') => void; linkData: LinkData; viewState?: ViewModel; } @@ -37,29 +37,59 @@ const handleNavigation = ( } }; const GeneResultsTable: React.FC = ({ - results, - onSortClick, - linkData, - viewState - }) => { + results, + onSortClick, + linkData, + viewState, +}) => { + const [sortField, setSortField] = useState(null); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + + const handleSort = (field: string) => { + const newSortOrder = sortField === field && sortOrder === 'asc' ? 'desc' : 'asc'; + setSortField(field); + setSortOrder(newSortOrder); + onSortClick(field, newSortOrder); + }; + return ( - + - + - - @@ -68,8 +98,8 @@ const GeneResultsTable: React.FC = ({ - +
Strain - handleSort('strain')} + className={`vf-table__heading ${styles.vfTableHeading} ${styles.clickableHeader}`}> + Strain + {sortField === 'strain' ? ( + + ) : ( + + )} + handleSort('gene_name')} + className={`vf-table__heading ${styles.vfTableHeading} ${styles.clickableHeader}`}> + Gene + {sortField === 'gene_name' ? ( + + ) : ( + + )} Gene - handleSort('locus_tag')} + className={`vf-table__heading ${styles.vfTableHeading} ${styles.clickableHeader}`}> + Locus Tag + {sortField === 'locus_tag' ? ( + + ) : ( + + )} + handleSort('product')} + className={`vf-table__heading ${styles.vfTableHeading}`}> + Product ProductLocus Tag Actions
{result.strain || 'Unknown Strain'} {result.gene_name || 'Unknown Gene Name'}{result.description || ''} {result.locus_tag || 'Unknown Locus Tag'}{result.description || ''} {viewState ? ( void, selectedSpecies?: number [], results: any[], - onSortClick: (sortField: string) => void, + onSortClick: (sortField: string, sortOrder: 'asc' | 'desc') => void; + sortField: string, + sortOrder: 'asc' | 'desc'; totalPages: number, currentPage: number, handlePageClick: (page: number) => void, @@ -31,7 +33,9 @@ const GeneSearchForm: React.FC = ({ onSortClick, selectedGenomes, linkData, - viewState + viewState, + sortField, + sortOrder }) => { const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState<{ @@ -41,8 +45,6 @@ const GeneSearchForm: React.FC = ({ }[]>([]); const [geneName, setGeneName] = useState(''); const [results, setResults] = useState([]); - const [currentSortField, setCurrentSortField] = useState(''); - const [currentSortOrder, setCurrentSortOrder] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [hasPrevious, setHasPrevious] = useState(false); @@ -55,7 +57,7 @@ const GeneSearchForm: React.FC = ({ }; useEffect(() => { - fetchSearchResults(1); + fetchSearchResults(1, sortField, sortOrder); }, [pageSize]); // Fetch suggestions for autocomplete based on the query and selected species @@ -110,7 +112,8 @@ const GeneSearchForm: React.FC = ({ // Fetch search results based on the query, selected species, page, sort field, and sort order const fetchSearchResults = useCallback( - async (page = 1, sortField = currentSortField, sortOrder = currentSortOrder) => { + async (page = 1, sortField: string, sortOrder: string) => { + console.log('111111111111') const params = new URLSearchParams({ 'query': query.trim() || '', 'page': String(page), @@ -156,13 +159,13 @@ const GeneSearchForm: React.FC = ({ setHasNext(false); } }, - [query, selectedSpecies, selectedGenomes, currentSortField, currentSortOrder, pageSize] + [query, selectedSpecies, selectedGenomes, sortField, sortOrder, pageSize] ); useEffect(() => { - fetchSearchResults(); - }, [selectedSpecies, selectedGenomes, pageSize]); + fetchSearchResults(1, sortField, sortOrder); + }, [selectedSpecies, selectedGenomes, sortField, sortOrder, pageSize]); const handleInputChange = (event: React.ChangeEvent) => { @@ -187,19 +190,13 @@ const GeneSearchForm: React.FC = ({ const handleSubmit = (event: React.FormEvent) => { console.log('selectedStrainId:' + selectedGeneId) event.preventDefault(); - fetchSearchResults(); + fetchSearchResults(currentPage, sortField, sortOrder); }; - const handleSortClick = (sortField: string) => { - const newSortOrder = currentSortField === sortField ? (currentSortOrder === 'asc' ? 'desc' : 'asc') : 'asc'; - setCurrentSortField(sortField); - setCurrentSortOrder(newSortOrder); - fetchSearchResults(1, sortField, newSortOrder); - }; const handlePageClick = (page: number) => { setCurrentPage(page); - fetchSearchResults(page); + fetchSearchResults(page, sortField, sortOrder); }; return ( diff --git a/dataportal-app/src/components/pages/GeneViewerPage.tsx b/dataportal-app/src/components/pages/GeneViewerPage.tsx index 51f9ffd..37ea4ae 100644 --- a/dataportal-app/src/components/pages/GeneViewerPage.tsx +++ b/dataportal-app/src/components/pages/GeneViewerPage.tsx @@ -53,6 +53,10 @@ const GeneViewerPage: React.FC = () => { const [geneCurrentPage, setGeneCurrentPage] = useState(1); const {geneId, genomeId} = useParams<{ geneId?: string; genomeId?: string }>(); + + const [sortField, setSortField] = useState('species'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const searchParams = new URLSearchParams(location.search); // const genomeId = searchParams.get('genomeId'); @@ -233,7 +237,9 @@ const GeneViewerPage: React.FC = () => { onSearchSubmit={handleGeneSearch} selectedGenomes={genomeId ? [{id: parseInt(genomeId, 10), name: ''}] : []} results={geneResults} - onSortClick={(sortField) => console.log('Sort by:', sortField)} + onSortClick={handleGeneSearch} + sortField={sortField} + sortOrder={sortOrder} currentPage={geneCurrentPage} totalPages={totalPages} handlePageClick={(page) => setGeneCurrentPage(page)} diff --git a/dataportal-app/src/components/pages/HomePage.tsx b/dataportal-app/src/components/pages/HomePage.tsx index d3b4159..1d22266 100644 --- a/dataportal-app/src/components/pages/HomePage.tsx +++ b/dataportal-app/src/components/pages/HomePage.tsx @@ -151,7 +151,7 @@ const HomePage: React.FC = () => { const newSortOrder = sortField === field && sortOrder === 'asc' ? 'desc' : 'asc'; setSortField(field); setSortOrder(newSortOrder); - console.log('Sorting Genes by:', {field, order: newSortOrder}); + console.log('HomePage Sorting Genes by:', {field, order: newSortOrder}); }; const linkData = { @@ -226,13 +226,13 @@ const HomePage: React.FC = () => { onGenomeSelect={handleGenomeSelect} selectedSpecies={selectedSpecies} onSortClick={handleGenomeSortClick} + sortField={sortField} + sortOrder={sortOrder} results={genomeResults} selectedGenomes={selectedGenomes} onToggleGenomeSelect={handleToggleGenomeSelect} currentPage={1} totalPages={1} - sortField={sortField} - sortOrder={sortOrder} handlePageClick={(page) => console.log('Page:', page)} /> )} @@ -246,6 +246,8 @@ const HomePage: React.FC = () => { selectedGenomes={selectedGenomes} results={geneResults} onSortClick={handleGeneSortClick} + sortField={sortField} + sortOrder={sortOrder} currentPage={1} totalPages={1} handlePageClick={(page) => console.log('Page:', page)} diff --git a/dataportal-app/src/services/geneService.ts b/dataportal-app/src/services/geneService.ts index ff61256..b676240 100644 --- a/dataportal-app/src/services/geneService.ts +++ b/dataportal-app/src/services/geneService.ts @@ -51,12 +51,13 @@ export const fetchGeneSearchResults = async ( selectedSpecies?: number [], ) => { try { + console.log('2222222222') const params = new URLSearchParams({ query: gene, page: String(page), per_page: String(perPage), - sortField, - sortOrder, + sort_field: sortField, + sort_order: sortOrder, }); // Add genome IDs if available diff --git a/dataportal_api/dataportal/api.py b/dataportal_api/dataportal/api.py index c1ecc6d..f1e635f 100644 --- a/dataportal_api/dataportal/api.py +++ b/dataportal_api/dataportal/api.py @@ -247,6 +247,9 @@ async def search_genes_by_multiple_genomes_and_species_and_string( sort_order: Optional[str] = "asc", ): try: + logger.debug( + f"Request received with params: query={query}, page={page}, per_page={per_page}, sortField={sort_field}, sortOrder={sort_order}" + ) return await gene_service.get_genes_by_multiple_genomes_and_string( genome_ids, species_id, query, page, per_page, sort_field, sort_order ) diff --git a/dataportal_api/dataportal/services/gene_service.py b/dataportal_api/dataportal/services/gene_service.py index 22a6e7d..e58e8a5 100644 --- a/dataportal_api/dataportal/services/gene_service.py +++ b/dataportal_api/dataportal/services/gene_service.py @@ -169,10 +169,33 @@ async def get_genes_by_multiple_genomes_and_string( if query: filters &= Q(gene_name__icontains=query.strip()) + valid_sort_fields = { + "gene_name": "gene_name", + "strain": "strain__isolate_name", + "description": "description", + "locus_tag": "locus_tag", + "product": "product", + } + + # Validate and map sort_field + sort_field_mapped = valid_sort_fields.get(sort_field, "gene_name") + if sort_field not in valid_sort_fields: + logger.warning( + f"Invalid sort_field '{sort_field}', defaulting to 'gene_name'" + ) + sort_field_mapped = "gene_name" + + logger.info( + f"Fetching genes with sort_field='{sort_field_mapped}', sort_order='{sort_order}'" + ) + + # Fetch paginated genes with sorting genes, total_results = await self._fetch_paginated_genes( - filters, page, per_page, sort_field, sort_order + filters, page, per_page, sort_field_mapped, sort_order ) + serialized_genes = [self._serialize_gene(gene) for gene in genes] + return self._create_pagination_schema( serialized_genes, page, per_page, total_results ) @@ -183,47 +206,6 @@ async def get_genes_by_multiple_genomes_and_string( logger.error(f"Error in search_genes_by_multiple_genomes_and_string: {e}") raise HttpError(500, "Internal Server Error") - async def search_genes_in_strain(self, strain_id: int, query: str, limit: int = 10): - try: - gene_filter = Q(strain_id=strain_id, gene_name__icontains=query) - genes = await sync_to_async( - lambda: list( - Gene.objects.filter(gene_filter).select_related("strain")[:limit] - ) - )() - return [ - {"gene_name": gene.gene_name, "description": gene.description} - for gene in genes - ] - except Exception as e: - logger.error(f"Error searching genes in strain: {e}") - return [] - - async def search_genes_globally(self, query: str, limit: int = 10): - try: - gene_filter = Q(gene_name__icontains=query) | Q( - description__icontains=query - ) - genes = await sync_to_async( - lambda: list( - Gene.objects.filter(gene_filter).select_related("strain")[:limit] - ) - )() - return [ - { - "gene_name": gene.gene_name, - "seq_id": gene.seq_id, - "strain": gene.strain.isolate_name if gene.strain else "Unknown", - "assembly": gene.strain.assembly_name if gene.strain else None, - "description": gene.description, - } - for gene in genes - ] - - except Exception as e: - logger.error(f"Error searching genes globally: {e}") - return [] - # helper methods def _serialize_gene(self, gene: Gene) -> dict: @@ -278,14 +260,18 @@ async def _fetch_paginated_genes( order_prefix = "-" if sort_order == "desc" else "" sort_by = f"{order_prefix}{sort_field}" if sort_field else "gene_name" - genes = await sync_to_async( - lambda: list( - Gene.objects.select_related("strain") - .filter(filter_criteria) - .order_by(sort_by)[start : start + per_page] - ) - )() - total_results = await sync_to_async( - Gene.objects.filter(filter_criteria).count - )() - return genes, total_results + try: + genes = await sync_to_async( + lambda: list( + Gene.objects.select_related("strain") + .filter(filter_criteria) + .order_by(sort_by)[start : start + per_page] + ) + )() + total_results = await sync_to_async( + Gene.objects.filter(filter_criteria).count + )() + return genes, total_results + except Exception as e: + logger.error(f"Error in _fetch_paginated_genes: {e}") + raise