diff --git a/README.md b/README.md index 3f6c29130..f0cc7da1b 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,10 @@ -# beebop - -## Docker Quick Start - -Run the dockerised app along with proxy and all dependencies: - -``` - ./scripts/run_docker_decrypt -``` - -You may need to update your version of Docker and Docker Compose: see [here](https://docs.docker.com/engine/install/ubuntu/) for instructions on updating on Ubuntu. - -By default this will configure the nginx proxy for host localhost. To deploy with a different hostname, pass it as an argument, e.g. -``` -./scripts/run_docker_decrypt beebop.dide.ic.ac.uk -``` - -This will also populate app config with secrets from the vault. If you are not running the script for the first time, -or not for the first time since running the app outside docker, you can omit this step by running the `run_docker` script. - -Bring down the app with -``` - ./scripts/stop_docker -``` +# Beebop Docker images are built on CI using `./proxy/docker/build`, `./app/server/docker/build`. If you want to generate them from changed local sources you can run those same scripts locally to build images. -To target a branch of `beebop_py`, set `API_IMAGE` in `scripts/common`. - -When running locally in docker, the backend is serving from `beebop_beebop-server_1`, and the front end from the proxy -container `beebop_proxy_1`. +To target a branch of `beebop_py`, set `API_IMAGE` in `scripts/common`. If there is a dev image available, +that can be targeting by adding `-dev` to the image name. ## Local development @@ -47,7 +22,7 @@ docker --version If you run the application for the first time (or for the first time after running in docker), you need to replace the -secrets in the config file in `app/server/src/resources` first. +secrets in the config file in `app/server/src/resources` first. Login to the vault: ``` export VAULT_ADDR=https://vault.dide.ic.ac.uk:8200 @@ -64,6 +39,12 @@ To start all required components, run: ./scripts/run_test ``` +To run dependencies and server only, run: +``` +./scripts/run_test server-only +``` +*Note: These scripts call `run_dependencies` which downloads ref databases only. To download full databases, remove `--refs` from the `./scripts/download_databases --refs` command in `run_dependencies`* + The website can be viewed at http://localhost:5173/ . You can stop the application with `./scripts/stop_test`. The `run_test` script uses [pm2](https://github.com/Unitech/pm2) to manage running the client and server applications. diff --git a/app/client-v2/package-lock.json b/app/client-v2/package-lock.json index aa3455e4e..eb3c0e72d 100644 --- a/app/client-v2/package-lock.json +++ b/app/client-v2/package-lock.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@pinia/testing": "^0.1.3", - "@playwright/test": "^1.42.0", + "@playwright/test": "^1.49.1", "@rushstack/eslint-patch": "^1.3.3", "@testing-library/jest-dom": "^6.4.2", "@testing-library/user-event": "^14.5.2", @@ -1190,18 +1190,18 @@ } }, "node_modules/@playwright/test": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz", - "integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "dependencies": { - "playwright": "1.42.0" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@rollup/pluginutils": { @@ -5675,33 +5675,33 @@ } }, "node_modules/playwright": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz", - "integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "dependencies": { - "playwright-core": "1.42.0" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz", - "integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/possible-typed-array-names": { diff --git a/app/client-v2/package.json b/app/client-v2/package.json index 2893a95c2..c184976b1 100644 --- a/app/client-v2/package.json +++ b/app/client-v2/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@pinia/testing": "^0.1.3", - "@playwright/test": "^1.42.0", + "@playwright/test": "^1.49.1", "@rushstack/eslint-patch": "^1.3.3", "@testing-library/jest-dom": "^6.4.2", "@testing-library/user-event": "^14.5.2", diff --git a/app/client-v2/src/__tests__/App.spec.ts b/app/client-v2/src/__tests__/App.spec.ts index 97c1e9867..abcb381fc 100644 --- a/app/client-v2/src/__tests__/App.spec.ts +++ b/app/client-v2/src/__tests__/App.spec.ts @@ -4,7 +4,6 @@ import { createTestingPinia } from "@pinia/testing"; import { render, screen } from "@testing-library/vue"; import { defineComponent } from "vue"; import { createRouter, createWebHistory } from "vue-router"; -import PrimeVue from "primevue/config"; const mockedThemeValues = { setInitialTheme: vitest.fn(), diff --git a/app/client-v2/src/__tests__/stores/projectStore.spec.ts b/app/client-v2/src/__tests__/stores/projectStore.spec.ts index 5a10f1668..da991b487 100644 --- a/app/client-v2/src/__tests__/stores/projectStore.spec.ts +++ b/app/client-v2/src/__tests__/stores/projectStore.spec.ts @@ -698,7 +698,33 @@ describe("projectStore", () => { [MOCK_PROJECT_SAMPLES[0].hash]: MOCK_PROJECT_SAMPLES[0].sketch, [MOCK_PROJECT_SAMPLES[1].hash]: MOCK_PROJECT_SAMPLES[1].sketch, [MOCK_PROJECT_SAMPLES[2].hash]: MOCK_PROJECT_SAMPLES[2].sketch - } + }, + amrForMetadataCsv: [ + { + "Chloramphenicol Resistance": "Probably", + "Cotrim Resistance": "Probably not", + "Erythromycin Resistance": "Probably not", + ID: "sample1-test-hash", + "Penicillin Resistance": "Unlikely", + "Tetracycline Resistance": "Highly unlikely" + }, + { + "Chloramphenicol Resistance": "Probably", + "Cotrim Resistance": "Almost certainly", + "Erythromycin Resistance": "Almost certainly", + ID: "sample2-test-hash", + "Penicillin Resistance": "Highly unlikely", + "Tetracycline Resistance": "Highly unlikely" + }, + { + "Chloramphenicol Resistance": "Probably", + "Cotrim Resistance": "Unlikely", + "Erythromycin Resistance": "Probably not", + ID: "sample3-test-hash", + "Penicillin Resistance": "Probably", + "Tetracycline Resistance": "Highly unlikely" + } + ] }); }); it("should get download url & download when downloadZip is called", async () => { diff --git a/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts b/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts index f81117a8d..0848b6ed9 100644 --- a/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts +++ b/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts @@ -53,11 +53,11 @@ describe("projectCsvUtils", () => { const result = convertAmrForCsv(amr); expect(result).toEqual({ - Penicillin: "word", - Chloramphenicol: "word", - Erythromycin: "word", - Tetracycline: "word", - Cotrim: "word" + "Penicillin Resistance": "word", + "Chloramphenicol Resistance": "word", + "Erythromycin Resistance": "word", + "Tetracycline Resistance": "word", + "Cotrim Resistance": "word" }); }); }); diff --git a/app/client-v2/src/components/ProjectView/CytoscapeCanvas.vue b/app/client-v2/src/components/ProjectView/CytoscapeCanvas.vue index 496551934..fead58185 100644 --- a/app/client-v2/src/components/ProjectView/CytoscapeCanvas.vue +++ b/app/client-v2/src/components/ProjectView/CytoscapeCanvas.vue @@ -29,14 +29,14 @@ onMounted(async () => { style: { width: "10px", height: "10px", - content: "data(key0)", + content: "data(d1)", "font-size": "7px", color: "#00CC66", "background-color": "rgba(45, 212, 191, 0.44)" } }, { - selector: 'node[ref_query = "query"]', + selector: 'node[d2 = "query"]', style: { "background-color": "crimson", color: "crimson" diff --git a/app/client-v2/src/components/ProjectView/NetworkTab.vue b/app/client-v2/src/components/ProjectView/NetworkTab.vue index a969c0329..6762eab80 100644 --- a/app/client-v2/src/components/ProjectView/NetworkTab.vue +++ b/app/client-v2/src/components/ProjectView/NetworkTab.vue @@ -20,6 +20,10 @@ const { data, error, isFetching } = useFetch(`${apiUrl}/networkGraphs/${store.pr
+ These graphs are pruned versions of the full graphs. To view full graphs, download the zip file and view the + .graphml externally. View in fullscreen, reset layout or use mouse, touchpad or touchscreen gestures on graphs to zoom in and out, or move nodes around. = {}; const names: Record = {}; + const amrForMetadataCsv: AMRMetadataCsv[] = []; let projectHashKey = ""; this.project.samples .sort((a, b) => a.filename.localeCompare(b.filename)) @@ -311,10 +314,18 @@ export const useProjectStore = defineStore("project", { projectHashKey += sample.hash + sample.filename; sketches[sample.hash] = sample.sketch; names[sample.hash] = sample.filename; + amrForMetadataCsv.push({ ID: sample.hash, ...convertAmrForCsv(sample.amr!!) }); }, ""); const projectHash = Md5.hashStr(projectHashKey); - return { projectHash, names, sketches, projectId: this.project.id, species: this.project.species }; + return { + projectHash, + names, + sketches, + projectId: this.project.id, + species: this.project.species, + amrForMetadataCsv + }; }, async downloadZip(type: AnalysisType, cluster: string) { diff --git a/app/client-v2/src/types/projectTypes.ts b/app/client-v2/src/types/projectTypes.ts index e1574a407..6321915b0 100644 --- a/app/client-v2/src/types/projectTypes.ts +++ b/app/client-v2/src/types/projectTypes.ts @@ -23,6 +23,17 @@ export interface AMR { species: boolean; } +export interface AMRForCsv { + "Penicillin Resistance": string; + "Chloramphenicol Resistance": string; + "Erythromycin Resistance": string; + "Tetracycline Resistance": string; + "Cotrim Resistance": string; +} + +export interface AMRMetadataCsv extends AMRForCsv { + ID: string; +} export interface ProjectSample { hash: string; filename: string; diff --git a/app/client-v2/src/utils/projectCsvUtils.ts b/app/client-v2/src/utils/projectCsvUtils.ts index 47602a014..4925c2072 100644 --- a/app/client-v2/src/utils/projectCsvUtils.ts +++ b/app/client-v2/src/utils/projectCsvUtils.ts @@ -1,4 +1,4 @@ -import type { AMR, ProjectSample } from "@/types/projectTypes"; +import type { AMR, AMRForCsv, ProjectSample } from "@/types/projectTypes"; import { convertProbabilityToWord } from "./amrDisplayUtils"; export const downloadCsv = (samples: ProjectSample[], filename: string) => { @@ -12,12 +12,12 @@ export const downloadCsv = (samples: ProjectSample[], filename: string) => { triggerCsvDownload(csvContent, `${filename}.csv`); }; -export const convertAmrForCsv = (amr: AMR) => ({ - Penicillin: convertProbabilityToWord(amr.Penicillin, "Penicillin"), - Chloramphenicol: convertProbabilityToWord(amr.Chloramphenicol, "Chloramphenicol"), - Erythromycin: convertProbabilityToWord(amr.Erythromycin, "Erythromycin"), - Tetracycline: convertProbabilityToWord(amr.Tetracycline, "Tetracycline"), - Cotrim: convertProbabilityToWord(amr.Trim_sulfa, "Cotrim") +export const convertAmrForCsv = (amr: AMR): AMRForCsv => ({ + "Penicillin Resistance": convertProbabilityToWord(amr.Penicillin, "Penicillin"), + "Chloramphenicol Resistance": convertProbabilityToWord(amr.Chloramphenicol, "Chloramphenicol"), + "Erythromycin Resistance": convertProbabilityToWord(amr.Erythromycin, "Erythromycin"), + "Tetracycline Resistance": convertProbabilityToWord(amr.Tetracycline, "Tetracycline"), + "Cotrim Resistance": convertProbabilityToWord(amr.Trim_sulfa, "Cotrim") }); export const generateCsvContent = (data: Record[]) => { diff --git a/app/server/src/controllers/indexController.ts b/app/server/src/controllers/indexController.ts index c826a6d26..9e91f4880 100644 --- a/app/server/src/controllers/indexController.ts +++ b/app/server/src/controllers/indexController.ts @@ -14,10 +14,10 @@ export default (config) => { async runPoppunk(request, response, next) { await asyncHandler(next, async () => { const poppunkRequest = request.body as BeebopRunRequest; - const {projectHash, projectId, names, sketches, species } = poppunkRequest; + const { projectHash, projectId, names, sketches, species, amrForMetadataCsv } = poppunkRequest; const {redis} = request.app.locals; await userStore(redis).saveHashAndSamplesRun(request, projectId, projectHash, names); - const apiRequest = { names, projectHash, sketches, species } as PoppunkRequest; + const apiRequest = { names, projectHash, sketches, species, amrForMetadataCsv } as PoppunkRequest; await axios .post(`${config.api_url}/poppunk`, apiRequest, { headers: { diff --git a/app/server/src/types/requestTypes.ts b/app/server/src/types/requestTypes.ts index c759d7880..be3199594 100644 --- a/app/server/src/types/requestTypes.ts +++ b/app/server/src/types/requestTypes.ts @@ -5,8 +5,20 @@ export interface PoppunkRequest { projectHash: string, sketches: Record species: string, + amrForMetadataCsv: AMRMetadataCsv[] } +export interface AMRForCsv { + "Penicillin Resistance": string; + "Chloramphenicol Resistance": string; + "Erythromycin Resistance": string; + "Tetracycline Resistance": string; + "Cotrim Resistance": string; +} + +export interface AMRMetadataCsv extends AMRForCsv { + ID: string; +} export interface BeebopRunRequest extends PoppunkRequest { projectId: string, } diff --git a/app/server/tests/integration/testSample.ts b/app/server/tests/integration/testSample.ts index 0b1ffa881..c3e3ecbb8 100644 --- a/app/server/tests/integration/testSample.ts +++ b/app/server/tests/integration/testSample.ts @@ -13135,4 +13135,14 @@ export const testSample = ( }, }, names: { fd38a3bc7197390fd3734240a67fb515: "7622_5#78.fa" }, + amrForMetadataCsv: [ + { + ID: "fd38a3bc7197390fd3734240a67fb515", + "Penicillin Resistance": "Highly unlikely", + "Chloramphenicol Resistance": "Unsure", + "Erythromycin Resistance": "Highly unlikely", + "Tetracycline Resistance": "Almost certainly", + "Cotrim Resistance": "Highly likely", + }, + ], }); diff --git a/scripts/common b/scripts/common index 403c69324..9004cca03 100755 --- a/scripts/common +++ b/scripts/common @@ -1,9 +1,9 @@ #!/usr/bin/env bash -export API_IMAGE="main" +export API_IMAGE="main" # TODO revert back to main before merge into main NETWORK=beebop_nw VOLUME=beebop-storage NAME_REDIS=beebop-redis NAME_API=beebop-py-api NAME_WORKER=beebop-py-worker -PORT=5000 \ No newline at end of file +PORT=5000