From eb7b907e965955babf3aa530001a1ef41eaeb5ad Mon Sep 17 00:00:00 2001 From: Dzmitry Hramyka Date: Tue, 5 Sep 2023 16:11:52 +0200 Subject: [PATCH] feat: Implementation of search for general entries (#20) (#37) --- backend/app/main.py | 24 + frontend/package-lock.json | 37 +- frontend/package.json | 1 + frontend/src/api/__tests__/utils.spec.ts | 4 +- frontend/src/api/annonars.ts | 7 + frontend/src/api/utils.ts | 20 +- frontend/src/components/HeaderDefault.vue | 2 +- frontend/src/components/HeaderDetailPage.vue | 20 +- .../VariantDetails/BeaconNetwork.vue | 2 +- .../VariantDetails/FreqsAutosomal.vue | 73 +- .../VariantDetails/FreqsMitochondrial.vue | 6 +- .../src/components/VariantDetails/TxCsq.vue | 6 +- .../VariantDetails/VariantConservation.vue | 4 +- .../VariantDetails/VariantFreqs.vue | 1 + .../VariantDetails/VariantTools.vue | 1265 ++++++++--------- .../VariantDetails/VariantValidator.vue | 329 ++--- .../__tests__/HeaderDefault.spec.ts | 2 +- frontend/src/router/index.ts | 6 + frontend/src/stores/geneInfo.ts | 6 +- frontend/src/stores/genesList.ts | 101 ++ frontend/src/stores/misc.ts | 9 +- frontend/src/views/GenesListView.vue | 137 ++ frontend/src/views/HomeView.vue | 19 +- frontend/src/views/__tests__/HomeView.spec.ts | 2 +- 24 files changed, 1176 insertions(+), 907 deletions(-) create mode 100644 frontend/src/stores/genesList.ts create mode 100644 frontend/src/views/GenesListView.vue diff --git a/backend/app/main.py b/backend/app/main.py index 0b691cbf..cf9a7b57 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -101,6 +101,30 @@ async def version(): return Response(content=version) +# Register app for returning proxy for variantvalidator.org. +@app.get("/variantvalidator/{path:path}") +async def variantvalidator(request: Request, path: str): + """Implement reverse proxy for variantvalidator.org.""" + url = request.url + # Change grch to GRCh and chr to nothing in path + path = path.replace("grch", "GRCh").replace("chr", "") + backend_url = "https://rest.variantvalidator.org/VariantValidator/variantvalidator/" + path + + backend_url = backend_url + (f"?{url.query}" if url.query else "") + backend_req = client.build_request( + method=request.method, + url=backend_url, + content=await request.body(), + ) + backend_resp = await client.send(backend_req, stream=True) + return StreamingResponse( + backend_resp.aiter_raw(), + status_code=backend_resp.status_code, + headers=backend_resp.headers, + background=BackgroundTask(backend_resp.aclose), + ) + + # Register route for favicon. @app.get("/favicon.ico") async def favicon(): diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 08a3c2f9..1890d042 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,6 +36,7 @@ "prettier": "^3.0.2", "typescript": "~5.2.2", "vite": "^4.3.9", + "vite-plugin-vuetify": "^1.0.2", "vitest": "^0.32.4", "vitest-fetch-mock": "^0.2.2", "vue-cli-plugin-vuetify": "~2.5.8", @@ -2424,8 +2425,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.7.1.tgz", "integrity": "sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "find-cache-dir": "^3.3.2", "upath": "^2.0.1" @@ -3452,8 +3452,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/concat-map": { "version": "0.0.1", @@ -4656,8 +4655,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -4674,8 +4672,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "semver": "^6.0.0" }, @@ -7811,8 +7808,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -8123,8 +8119,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "find-up": "^4.0.0" }, @@ -8136,8 +8131,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -8150,8 +8144,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -8163,8 +8156,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "p-try": "^2.0.0" }, @@ -8179,8 +8171,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -10138,8 +10129,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=4", "yarn": "*" @@ -10377,8 +10367,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-1.0.2.tgz", "integrity": "sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "@vuetify/loader-shared": "^1.7.1", "debug": "^4.3.3", diff --git a/frontend/package.json b/frontend/package.json index 53e94b77..d563229b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,6 +42,7 @@ "prettier": "^3.0.2", "typescript": "~5.2.2", "vite": "^4.3.9", + "vite-plugin-vuetify": "^1.0.2", "vitest": "^0.32.4", "vitest-fetch-mock": "^0.2.2", "vue-cli-plugin-vuetify": "~2.5.8", diff --git a/frontend/src/api/__tests__/utils.spec.ts b/frontend/src/api/__tests__/utils.spec.ts index 7854379b..a4766e0f 100644 --- a/frontend/src/api/__tests__/utils.spec.ts +++ b/frontend/src/api/__tests__/utils.spec.ts @@ -36,11 +36,11 @@ describe('roundIt method', () => { describe('search method', () => { it('should return route location if match', () => { - const result = search('BRCA1', 'ghcr37') + const result = search('HGNC:1100', 'ghcr37') expect(result).toEqual({ name: 'gene', params: { - searchTerm: 'BRCA1', + searchTerm: 'HGNC:1100', genomeRelease: 'ghcr37' } }) diff --git a/frontend/src/api/annonars.ts b/frontend/src/api/annonars.ts index 29d8e905..aa302ecf 100644 --- a/frontend/src/api/annonars.ts +++ b/frontend/src/api/annonars.ts @@ -40,4 +40,11 @@ export class AnnonarsClient { }) return await response.json() } + + async fetchGenes(query: string): Promise { + const response = await fetch(`${this.apiBaseUrl}genes/search?${query}`, { + method: 'GET' + }) + return await response.json() + } } diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index 11e7a455..4b794025 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -83,11 +83,13 @@ export const isVariantMtHomopolymer = (smallVar: any): any => { * the correct page. * * @param searchTerm The search term to use. + * @param genomeRelease The genome release to use. */ export const search = (searchTerm: string, genomeRelease: string) => { interface RouteLocationFragment { name: string params?: any + query?: any } type RouteLoctionBuilder = () => RouteLocationFragment @@ -96,9 +98,9 @@ export const search = (searchTerm: string, genomeRelease: string) => { // first match. const SEARCH_REGEXPS: [RegExp, RouteLoctionBuilder][] = [ [ - /^chr\d+:\d+:[A-Z]:[A-Z]$/, + /^HGNC:\d+$/, (): RouteLocationFragment => ({ - name: 'variant', + name: 'gene', params: { searchTerm: searchTerm, genomeRelease: genomeRelease @@ -106,14 +108,24 @@ export const search = (searchTerm: string, genomeRelease: string) => { }) ], [ - /^.*$/, + /^chr\d+:\d+:[A-Z]:[A-Z]$/, (): RouteLocationFragment => ({ - name: 'gene', + name: 'variant', params: { searchTerm: searchTerm, genomeRelease: genomeRelease } }) + ], + [ + /^.*$/, + (): RouteLocationFragment => ({ + name: 'genes', + query: { + q: searchTerm, + fields: 'hgnc_id,ensembl_gene_id,ncbi_gene_id,symbol' + } + }) ] ] diff --git a/frontend/src/components/HeaderDefault.vue b/frontend/src/components/HeaderDefault.vue index 21f1516f..05e0da3d 100644 --- a/frontend/src/components/HeaderDefault.vue +++ b/frontend/src/components/HeaderDefault.vue @@ -22,7 +22,7 @@ onMounted(() => { alt="logo" width="70" /> - Explanation and Evaluation of Variants + REEV: Explanation and Evaluation of Variants diff --git a/frontend/src/components/HeaderDetailPage.vue b/frontend/src/components/HeaderDetailPage.vue index fcb19ff9..166b8188 100644 --- a/frontend/src/components/HeaderDetailPage.vue +++ b/frontend/src/components/HeaderDetailPage.vue @@ -1,19 +1,31 @@