From 3bf3c008b4d8abeb032631d7ccf65452fea0f539 Mon Sep 17 00:00:00 2001 From: Angelina Uno-Antonison Date: Thu, 5 Oct 2023 14:23:22 -0500 Subject: [PATCH] Q3 2023 version update audit (#142) --- .../build-and-publish-docker-images.yml | 4 +- .github/workflows/common-static-analysis.yml | 6 +- .../workflows/development-main-deployment.yml | 2 +- .github/workflows/draft-paper.yml | 4 +- .github/workflows/nodejs.yml | 4 +- .github/workflows/python.yml | 4 +- .github/workflows/system-tests.yml | 6 +- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 6 +- backend/.pylintrc | 4 +- backend/Dockerfile | 4 +- backend/README.md | 16 +- backend/requirements.txt | 18 +- backend/src/config.py | 7 +- backend/src/models/analysis.py | 50 +- backend/src/models/phenotips_json.py | 26 +- backend/src/repository/analysis_collection.py | 2 +- backend/src/routers/analysis_router.py | 4 +- backend/src/security/oauth2.py | 2 +- .../integration/test_analysis_routers.py | 55 +- .../tests/integration/test_auth_routers.py | 8 +- backend/tests/test_utils.py | 7 + .../fast-api-prototype/Requirements.txt | 2 +- frontend/Dockerfile | 6 +- frontend/README.md | 6 +- frontend/package.json | 30 +- .../AnnotationView/AllianceGenomeCard.vue | 4 +- .../components/AnnotationView/CardDataset.vue | 4 +- .../test/views/AnalysisListingView.spec.js | 4 +- frontend/yarn.lock | 1795 ++++++----------- .../e2e/case_annotation_section_anchors.cy.js | 3 +- .../e2e/case_supporting_evidence.cy.js | 24 +- system-tests/e2e/rosalution_analysis.cy.js | 21 +- .../view_collapsible_case_information.cy.js | 4 +- system-tests/package.json | 6 +- system-tests/yarn.lock | 307 +-- 37 files changed, 988 insertions(+), 1471 deletions(-) diff --git a/.github/workflows/build-and-publish-docker-images.yml b/.github/workflows/build-and-publish-docker-images.yml index 77df4320..a6f7ac17 100644 --- a/.github/workflows/build-and-publish-docker-images.yml +++ b/.github/workflows/build-and-publish-docker-images.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.base.ref }} - name: Log in to the Container Registry - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/common-static-analysis.yml b/.github/workflows/common-static-analysis.yml index c33bce5e..5e62d7a9 100644 --- a/.github/workflows/common-static-analysis.yml +++ b/.github/workflows/common-static-analysis.yml @@ -3,9 +3,9 @@ on: [push, pull_request, workflow_dispatch] env: MARKDOWNLINT_CONFIG: etc/static-analysis/markdownlint.json - HADOLINT_DOCKER: hadolint/hadolint:v2.8.0 + HADOLINT_DOCKER: hadolint/hadolint:v2.12.0 SHELLCHECK_DOCKER: koalaman/shellcheck:v0.9.0 - MARKDOWN_DOCKER: ghcr.io/igorshubovych/markdownlint-cli:v0.32.2 + MARKDOWN_DOCKER: ghcr.io/igorshubovych/markdownlint-cli:v0.37.0 jobs: docker-shell-markdown-static-analysis: @@ -13,7 +13,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Pulling Docker containers run: | docker pull $HADOLINT_DOCKER diff --git a/.github/workflows/development-main-deployment.yml b/.github/workflows/development-main-deployment.yml index 200132f0..93e24a6b 100644 --- a/.github/workflows/development-main-deployment.yml +++ b/.github/workflows/development-main-deployment.yml @@ -22,7 +22,7 @@ jobs: runs-on: self-hosted steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: HEAD^ diff --git a/.github/workflows/draft-paper.yml b/.github/workflows/draft-paper.yml index ecab15f5..66c673fd 100644 --- a/.github/workflows/draft-paper.yml +++ b/.github/workflows/draft-paper.yml @@ -15,14 +15,14 @@ jobs: name: JOSS pandoc paper draft steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build draft PDF uses: openjournals/openjournals-draft-action@master with: journal: joss paper-path: paper.md - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: paper path: paper.pdf diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 5bd5145e..8be25361 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -14,10 +14,10 @@ jobs: strategy: matrix: - node-version: [16.2] + node-version: [20.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e196a7ac..9579b69d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -16,14 +16,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8"] + python-version: ["3.11"] defaults: run: working-directory: ./backend steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index b9753a7e..ccbd06da 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -8,15 +8,15 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Running the setup.sh script run: | sh setup.sh - name: Cypress tests - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@v6 with: working-directory: system-tests - browser: electron + browser: chrome headed: false start: docker compose up --build -d wait-on: 'http://local.rosalution.cgds' diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index f338074e..37de7803 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -48,7 +48,7 @@ This Code of Conduct applies within all community spaces, and also applies when ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders - responsible for enforcement at cgds@uabmc.edu. All complaints will be reviewed and investigated + responsible for enforcement at `cgds@uabmc.edu`. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a291d15..b8fa8de7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,4 +76,4 @@ We will review your issue and work to resolve the bug as soon as possible. Thank ## Seeking Support -For support in setting up and using Rosalution, please feel free to either use GitHub Issues or contact us via email on cgds@uabmc.edu. +For support in setting up and using Rosalution, please feel free to either use GitHub Issues or contact us via email on `cgds@uabmc.edu`. diff --git a/README.md b/README.md index 7b63da22..329d29d7 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ The following pre-requisites are required to be installed in the target *NIX env deploying and testing Rosalution. Install environment dependencies below using the respective installation instructions for your target environment. -- [Node.JS 16+](https://nodejs.org/en/) & [Classic Yarn](https://classic.yarnpkg.com/en/) +- [Node.JS 20.8+](https://nodejs.org/en/) & [Classic Yarn](https://classic.yarnpkg.com/en/) - Node.JS recommends managing Node.JS installations with [nvm](https://www.npmjs.com/package/npx) - [install](https://github.com/nvm-sh/nvm#install--update-script) - Yarn is not included with Node.JS with `nvm`. Run `npm install --global yarn` once Node.JS is installed. - [install](https://classic.yarnpkg.com/en/docs/install) -- [Python 3.8+](https://www.python.org/) - [Install](https://www.python.org/downloads/) +- [Python 3.11+](https://www.python.org/) - [Install](https://www.python.org/downloads/) - `pip3` to install the required packages for development within a virtual environment - `python venv` Some system installations of Python 3+ do not include python virtual environments that were added in Python 3.3+. Additional installation and setup may be necessary if using Python packaged with an OS (such as Ubuntu). @@ -458,7 +458,7 @@ To report a bug, refer to [🐞Reporting Issues](CONTRIBUTING.md#reporting-issue ## Maintainers - [Angelina Uno-Antonison](https://github.com/SeriousHorncat) - - Email: aeunoantonison@uabmc.edu + - Email: `aeunoantonison@uabmc.edu` - [Rabab Fatima](https://github.com/fatimarabab) - [James Scherer](https://github.com/JmScherer) diff --git a/backend/.pylintrc b/backend/.pylintrc index 50646aab..16658cec 100644 --- a/backend/.pylintrc +++ b/backend/.pylintrc @@ -310,8 +310,8 @@ min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [FORMAT] diff --git a/backend/Dockerfile b/backend/Dockerfile index d20e3a61..54d58c12 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # Local Development Stage -FROM python:3.9.10 as development-stage +FROM python:3.11-slim-bookworm as development-stage WORKDIR /app COPY requirements.txt /app/requirements.txt RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt @@ -7,7 +7,7 @@ COPY ./src /app/src ENTRYPOINT ["/bin/sh", "-c", "uvicorn src.main:app --host 0.0.0.0 --port 8000 --log-level info --reload"] # Production Build Stage -FROM python:3.9.10 as production-stage +FROM python:3.11-slim-bookworm as production-stage WORKDIR /app COPY logging.conf /app/logging.conf COPY requirements.txt /app/requirements.txt diff --git a/backend/README.md b/backend/README.md index 447078e5..19b73cd1 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # Rosalution Backend -Rosalution's backend uses FastAPI as a Python REST endpoint framework to accept and proccess frontend and user requests. +Rosalution's backend uses FastAPI as a Python REST endpoint framework to accept and process frontend and user requests. It is currently used to handle Rosalution's authentication system, interact with MongoDB for state management, and the web accessible Swagger API documentation. @@ -9,7 +9,7 @@ and the web accessible Swagger API documentation. ### Dependencies -- [Python 3.8+](https://www.python.org/) - [Install](https://www.python.org/downloads/) +- [Python 3.11](https://www.python.org/) - [Install](https://www.python.org/downloads/) - [Pip](https://pip.pypa.io/en/{"originTabId":1,"originWindowId":1}stable/) - [Install](https://pip.pypa.io/en/stable/installation/) ### Requirements @@ -25,7 +25,7 @@ isolated virtual environments for these projects. All packages necessary for Rosalution development are installed into the `./backend/rosalution_env/` virtual environment in the setup.sh script. -To create this isolation we use the python virtual environment [venv](https://docs.python.org/3.8/library/venv.html). +To create this isolation we use the python virtual environment [venv](https://docs.python.org/3.11/library/venv.html). Refer to the python virtual environment for documentation. Note: Make sure setup.sh script is run as this installs the rosalution_env and all it's dependencies. @@ -44,7 +44,7 @@ of startup. - **ROSALUTION_ENV** Sets whether the application's environment is in production. This will run the backend with the [-O flag](https://docs.python.org/3/using/cmdline.html#cmdoption-O) which will turn off `__debug__` statements -within the backend codebase when using the 'entrypoint-init.sh` to start the applicaiton. +within the backend codebase when using the 'entrypoint-init.sh` to start the application. - **MONGODB_HOST** Sets the host or host:port for the server host address for MongoDB. (default) rosalution-db - The default is the **docker compose** name for the service, so inside other docker containers within the same network, @@ -70,13 +70,17 @@ If another entity has or wishes to employ a CAS authority, the defined configura - This is not CAS specific, but it is employed when CAS fails and redirects the user to a specific url in the app -**cas_api_service_url**: str = "http://dev.cgds.uab.edu/rosalution/api/auth/login?nexturl=%2F" +```python +cas_api_service_url: str = "http://dev.cgds.uab.edu/rosalution/api/auth/login?nexturl=%2F" +``` - The application's url and nexturl defines where to redirect when login is successful - **nexturl** is a CAS parameter that tells the server where to redirect to in your application when completing the CAS interaction. The **nexturl** parameter will use a relative path. -**cas_server_url**: str = "https://padlockdev.idm.uab.edu/cas/" +```python +cas_server_url: str = "https://padlockdev.idm.uab.edu/cas/" +``` - Defines where the CAS url can be reached diff --git a/backend/requirements.txt b/backend/requirements.txt index 0392e5ae..6c34f6ac 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,18 +1,18 @@ -fastapi==0.74.1 -uvicorn[standard]==0.22.0 +fastapi[all]==0.103.2 +uvicorn[standard]==0.23.2 python-cas==1.6.0 itsdangerous==2.1.2 -pymongo==4.3.3 -jq==1.4.0 +pymongo==4.5.0 +jq==1.6.0 -python-multipart==0.0.5 +python-multipart==0.0.6 python-jose[cryptography]==3.3.0 passlib==1.7.4 bcrypt==4.0.1 # dev pytest-cov==4.0.0 -pytest==7.2.1 -pylint==2.15.10 -requests==2.28.2 -yapf==0.32.0 \ No newline at end of file +pytest==7.4.2 +pylint==3.0.0 +requests==2.31.0 +yapf==0.40.2 \ No newline at end of file diff --git a/backend/src/config.py b/backend/src/config.py index 95b09591..cc367513 100644 --- a/backend/src/config.py +++ b/backend/src/config.py @@ -5,7 +5,8 @@ """ # pylint: disable=too-few-public-methods from functools import lru_cache -from pydantic import BaseSettings, root_validator +from pydantic import model_validator +from pydantic_settings import BaseSettings class Settings(BaseSettings): @@ -18,7 +19,7 @@ class Settings(BaseSettings): mongodb_host: str = "rosalution-db" mongodb_db: str = "rosalution_db" rosalution_key: str - auth_web_failure_redirect_route = "/login" + auth_web_failure_redirect_route: str = "/login" oauth2_access_token_expire_minutes: int = 60 * 24 * 8 # 60 minutes * 24 hours * 8 days = 8 days oauth2_algorithm: str = "HS256" openapi_api_token_route: str = "auth/token" @@ -26,7 +27,7 @@ class Settings(BaseSettings): cas_server_url: str = "https://padlockdev.idm.uab.edu/cas/" cas_login_enable: bool = False - @root_validator(pre=True) + @model_validator(mode="before") @classmethod def rosalution_key_exists(cls, values): """ diff --git a/backend/src/models/analysis.py b/backend/src/models/analysis.py index 31312b7f..afb38f8e 100644 --- a/backend/src/models/analysis.py +++ b/backend/src/models/analysis.py @@ -7,7 +7,7 @@ from multiprocessing import Event import re from typing import List, Optional -from pydantic import BaseModel, root_validator +from pydantic import BaseModel, computed_field from .event import Event @@ -34,29 +34,39 @@ class BaseAnalysis(BaseModel): """The share parts of an analysis and it's summary""" name: str - description: Optional[str] + description: Optional[str] = None nominated_by: str - latest_status: Optional[StatusType] - created_date: Optional[date] - last_modified_date: Optional[date] timeline: List[Event] = [] third_party_links: Optional[List] = [] - # The structure of the root_validator from pydantic requires the method to be setup this way even if there is no - # self being used and no self argument - @root_validator - def compute_dates_and_status(cls, values): #pylint: disable=no-self-argument - """Computes the dates and status of an analysis from a timeline""" - if len(values['timeline']) == 0: - return values - - last_event = sorted(values['timeline'], key=lambda event: event.timestamp, reverse=True)[0] - values['last_modified_date'] = last_event.timestamp.date() - values['created_date'] = next( - (event.timestamp.date() for event in values['timeline'] if event.event == EventType.CREATE), None - ) - values['latest_status'] = StatusType.from_event(last_event.event) - return values + @computed_field + @property + def created_date(self) -> date: + """The created date derived from the create event in the timeline""" + if len(self.timeline) == 0: + return None + + return next((event.timestamp.date() for event in self.timeline if event.event == EventType.CREATE), None) + + @computed_field + @property + def last_modified_date(self) -> date: + """The last modified date derived from the last event in the timeline""" + if len(self.timeline) == 0: + return None + + last_event = sorted(self.timeline, key=lambda event: event.timestamp, reverse=True)[0] + return last_event.timestamp.date() + + @computed_field + @property + def latest_status(self) -> StatusType: + """The status as calculated from the events on the timeline""" + if len(self.timeline) == 0: + return None + + last_event = sorted(self.timeline, key=lambda event: event.timestamp, reverse=True)[0] + return StatusType.from_event(last_event.event) class AnalysisSummary(BaseAnalysis): diff --git a/backend/src/models/phenotips_json.py b/backend/src/models/phenotips_json.py index f470a786..c154d199 100644 --- a/backend/src/models/phenotips_json.py +++ b/backend/src/models/phenotips_json.py @@ -5,11 +5,12 @@ # pylint: disable=too-few-public-methods from datetime import datetime from typing import List, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict class PhenotipsVariants(BaseModel): """Models a variant within a Phenotips json import""" + model_config = ConfigDict(extra='ignore') gene: Optional[str] = None inheritance: Optional[str] = None @@ -20,14 +21,10 @@ class PhenotipsVariants(BaseModel): cdna: Optional[str] = None reference_genome: str - class config: # pylint: disable=invalid-name - """Configures the pydantic model""" - - extra = Extra.allow - class PhenotipsGene(BaseModel): """Models a gene within a Phenotips json genes""" + model_config = ConfigDict(extra='ignore') comments: Optional[str] = None gene: str @@ -35,34 +32,21 @@ class PhenotipsGene(BaseModel): strategy: Optional[list] = None status: Optional[str] = None - class config: # pylint: disable=invalid-name - """Configures the pydantic model""" - - extra = Extra.allow - class PhenotipsHpoTerm(BaseModel): """Models a gene within a Phenotips json genes""" + model_config = ConfigDict(extra='ignore') id: str label: str = "" - class config: # pylint: disable=invalid-name - """Configures the pydantic model""" - - extra = Extra.allow - class BasePhenotips(BaseModel): """The share parts of a phenotips and it's summary""" + model_config = ConfigDict(extra='ignore') date: datetime external_id: str variants: List[PhenotipsVariants] = [] genes: List[PhenotipsGene] = [] features: List[PhenotipsHpoTerm] = [] - - class config: # pylint: disable=invalid-name - """Configures the pydantic model""" - - extra = Extra.allow diff --git a/backend/src/repository/analysis_collection.py b/backend/src/repository/analysis_collection.py index 4adcd116..33bd722e 100644 --- a/backend/src/repository/analysis_collection.py +++ b/backend/src/repository/analysis_collection.py @@ -364,7 +364,7 @@ def update_event(self, analysis_name: str, username: str, event_type: EventType) analysis = self.collection.find_one({"name": analysis_name}) if not analysis: raise ValueError(f"Analysis with name {analysis_name} does not exist.") - analysis['timeline'].append(Event.timestamp_event(username, event_type).dict()) + analysis['timeline'].append(Event.timestamp_event(username, event_type).model_dump()) updated_document = self.collection.find_one_and_update( {"name": analysis_name}, diff --git a/backend/src/routers/analysis_router.py b/backend/src/routers/analysis_router.py index 5f3b9e73..77127801 100644 --- a/backend/src/routers/analysis_router.py +++ b/backend/src/routers/analysis_router.py @@ -76,8 +76,8 @@ async def create_file( phenotips_importer = PhenotipsImporter(repositories["analysis"], repositories["genomic_unit"]) try: - new_analysis = phenotips_importer.import_phenotips_json(phenotips_input.dict()) - new_analysis['timeline'].append(Event.timestamp_create_event(username).dict()) + new_analysis = phenotips_importer.import_phenotips_json(phenotips_input.model_dump()) + new_analysis['timeline'].append(Event.timestamp_create_event(username).model_dump()) repositories['analysis'].create_analysis(new_analysis) except ValueError as exception: diff --git a/backend/src/security/oauth2.py b/backend/src/security/oauth2.py index 6ff88fea..a3bd8d9c 100644 --- a/backend/src/security/oauth2.py +++ b/backend/src/security/oauth2.py @@ -71,7 +71,7 @@ class OAuth2ClientCredentialsRequestForm: def __init__( self, - grant_type: str = Form(None, regex="client_credentials"), + grant_type: str = Form(None, pattern="client_credentials"), scope: str = Form(""), client_id: Optional[str] = Form(None), client_secret: Optional[str] = Form(None), diff --git a/backend/tests/integration/test_analysis_routers.py b/backend/tests/integration/test_analysis_routers.py index 57a31e96..e9ca9af3 100644 --- a/backend/tests/integration/test_analysis_routers.py +++ b/backend/tests/integration/test_analysis_routers.py @@ -1,7 +1,6 @@ """Analysis Routes Integration test""" import json -import os import datetime from unittest.mock import patch @@ -12,7 +11,7 @@ from src.core.annotation import AnnotationService -from ..test_utils import read_database_fixture, read_test_fixture +from ..test_utils import fixture_filepath, read_database_fixture, read_test_fixture def test_get_analyses(client, mock_access_token, mock_repositories): @@ -61,16 +60,9 @@ def test_import_analysis_with_phenotips_json( ) mock_repositories['genomic_unit'].collection.find.return_value = read_database_fixture("genomic-units.json") - # This is used here because the 'read_fixture' returns a json dict rather than raw binary - # We actually want to send a binary file through the endpoint to simulate a file being sent - # then json.loads is used on the other end in the repository. - # This'll get updated and broken out in the test_utils in the future - path_to_current_file = os.path.realpath(__file__) - current_directory = os.path.split(path_to_current_file)[0] - path_to_file = os.path.join(current_directory, '../fixtures/' + 'phenotips-import.json') - with patch.object(BackgroundTasks, "add_task", return_value=None) as mock_background_add_task: - with open(path_to_file, "rb") as phenotips_file: + analysis_import_json_filepath = fixture_filepath('phenotips-import.json') + with open(analysis_import_json_filepath, "rb") as phenotips_file: response = client.post( "/analysis/import_file", headers={"Authorization": "Bearer " + mock_access_token}, @@ -236,31 +228,21 @@ def test_attach_image_to_pedigree_section(client, mock_access_token, mock_reposi mock_repositories['analysis'].collection.find_one_and_update.return_value = expected mock_repositories['bucket'].bucket.put.return_value = "633afb87fb250a6ea1569555" - # This is used here because the 'read_fixture' returns a json dict rather than raw binary - # We actually want to send a binary file through the endpoint to simulate a file being sent - # then json.loads is used on the other end in the repository. - # This'll get updated and broken out in the test_utils in the future - path_to_current_file = os.path.realpath(__file__) - current_directory = os.path.split(path_to_current_file)[0] - path_to_file = os.path.join(current_directory, '../fixtures/' + 'pedigree-fake.jpg') - - with open(path_to_file, "rb") as phenotips_file: - pedigree_image = phenotips_file.read() - pedigree_bytes = bytearray(pedigree_image) + section_image_filepath = fixture_filepath('pedigree-fake.jpg') + with open(section_image_filepath, "rb") as phenotips_file: response = client.post( "/analysis/CPAM0112/section/attach/image", headers={"Authorization": "Bearer " + mock_access_token}, - files={"upload_file": ("pedigree-fake.jpg", pedigree_bytes)}, + files={"upload_file": ("pedigree-fake.jpg", phenotips_file)}, data=({"section_name": "Pedigree", "field_name": "Pedigree"}) ) phenotips_file.close() + assert response.status_code == 201 mock_repositories["analysis"].collection.find_one_and_update.assert_called_with({"name": "CPAM0112"}, {"$set": expected}) - assert response.status_code == 201 - def test_update_existing_pedigree_section_image(client, mock_access_token, mock_repositories): """ Testing the update pedigree attachment endpoint """ @@ -269,24 +251,16 @@ def test_update_existing_pedigree_section_image(client, mock_access_token, mock_ mock_analysis = read_test_fixture("analysis-CPAM0002.json") mock_repositories["analysis"].collection.find_one_and_update.return_value = mock_analysis - # This is used here because the 'read_fixture' returns a json dict rather than raw binary - # We actually want to send a binary file through the endpoint to simulate a file being sent - # then json.loads is used on the other end in the repository. - # This'll get updated and broken out in the test_utils in the future - path_to_current_file = os.path.realpath(__file__) - current_directory = os.path.split(path_to_current_file)[0] - path_to_file = os.path.join(current_directory, '../fixtures/' + 'pedigree-fake.jpg') - - with open(path_to_file, 'rb') as file: - pedigree_image = file.read() - pedigree_bytes = bytearray(pedigree_image) + # Need to send the file as raw binary instead of the processed content + section_image_filepath = fixture_filepath('pedigree-fake.jpg') + with open(section_image_filepath, "rb") as image_file: response = client.put( "/analysis/CPAM0002/section/update/633afb87fb250a6ea1569555", headers={"Authorization": "Bearer " + mock_access_token}, - files={"upload_file": ("pedigree-fake.jpg", pedigree_bytes)}, + files={"upload_file": ("pedigree-fake.jpg", image_file)}, data=({"section_name": "Pedigree", "field_name": "Pedigree"}) ) - file.close() + image_file.close() expected = {'section': 'Pedigree', 'field': 'Pedigree', 'image_id': '633afb87fb250a6ea1569555'} @@ -299,10 +273,11 @@ def test_remove_existing_pedigree_section_image(client, mock_access_token, mock_ mock_repositories["analysis"].collection.find_one.return_value = read_test_fixture("analysis-CPAM0002.json") mock_repositories["bucket"].bucket.delete.return_value = None - response = client.delete( + response = client.request( + 'DELETE', "/analysis/CPAM0002/section/remove/63505be22888347cf1c275db", headers={"Authorization": "Bearer " + mock_access_token}, - data=({"section_name": "Pedigree", "field_name": "Pedigree"}) + data={"section_name": "Pedigree", "field_name": "Pedigree"}, ) mock_repositories["bucket"].bucket.delete.assert_called_with(ObjectId("63505be22888347cf1c275db")) diff --git a/backend/tests/integration/test_auth_routers.py b/backend/tests/integration/test_auth_routers.py index 5190a44c..2ba365ef 100644 --- a/backend/tests/integration/test_auth_routers.py +++ b/backend/tests/integration/test_auth_routers.py @@ -50,10 +50,7 @@ def mock_verify_return(paramater): #pylint: disable=unused-argument def test_logout(client): """ This tests functionality of the local logout function """ - response = client.get( - "/auth/logout", - cookies={"session": create_session_cookie({"username": "UABProvider"})}, - ) + response = client.get("/auth/logout",) assert response.json() == {"access_token": ""} @@ -63,7 +60,6 @@ def test_cas_enabled_logout(client, mock_settings): # pylint: disable=unused-ar response = client.get( '/auth/logout', headers={"host": 'dev.cgds.uab.edu'}, - cookies={"session": create_session_cookie({"username": "UABProvider"})} ) assert response.json() == { @@ -79,7 +75,7 @@ def test_logout_callback(client): and redirects the user to login """ - response = client.get('/auth/logout_callback', allow_redirects=False) + response = client.get('/auth/logout_callback', follow_redirects=False) assert response.status_code == 307 assert response.headers['location'] == 'http://dev.cgds.uab.edu/rosalution/login' diff --git a/backend/tests/test_utils.py b/backend/tests/test_utils.py index 69575080..836b748f 100644 --- a/backend/tests/test_utils.py +++ b/backend/tests/test_utils.py @@ -8,6 +8,13 @@ UNIT_TEST_FIXTURE_PATH = "./fixtures/" +def fixture_filepath(filename): + """Returns the fixture data binary""" + path_to_current_file = os.path.realpath(__file__) + current_directory = os.path.split(path_to_current_file)[0] + return os.path.join(current_directory, UNIT_TEST_FIXTURE_PATH + filename) + + def read_database_fixture(fixture_filename): """reads the JSON from the filepath relative to the database fixtures in etc""" return read_fixtures(DATABSE_FIXTURE_PATH, fixture_filename) diff --git a/docs/prototypes/fast-api-prototype/Requirements.txt b/docs/prototypes/fast-api-prototype/Requirements.txt index 5c333fbc..41788cfc 100644 --- a/docs/prototypes/fast-api-prototype/Requirements.txt +++ b/docs/prototypes/fast-api-prototype/Requirements.txt @@ -2,4 +2,4 @@ fastapi==0.74.1 uvicorn==0.17.5 pytest==7.0.1 pylint==2.12.2 -requests==2.26.0 \ No newline at end of file +requests==2.31.0 \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 474e3310..c9dcdc6a 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Local development stage -FROM node:16.2-alpine3.12 as development-stage +FROM node:20.8-alpine3.18 as development-stage WORKDIR /app COPY package.json /app/ COPY yarn.lock /app/ @@ -10,7 +10,7 @@ EXPOSE 3000 ENTRYPOINT ["yarn", "dev:host"] # Production Build stage -FROM node:16.2-alpine3.12 as production-build +FROM node:20.8-alpine3.18 as production-build WORKDIR /app COPY ./src /app/src/ COPY package.json /app/ @@ -23,7 +23,7 @@ ENV VITE_ROSALUTION_VERSION=$VERSION_BUILD_TAG RUN yarn install --frozen-lockfile && yarn build --base=/rosalution/ -FROM nginx:1.17.6-alpine as production-stage +FROM nginx:1.25.2-alpine3.18 as production-stage COPY etc/default.conf /etc/nginx/conf.d/ COPY --from=production-build /app/dist/ /usr/share/nginx/html/ diff --git a/frontend/README.md b/frontend/README.md index 8edbaf42..6f10ce31 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -8,7 +8,7 @@ for analysis. ### Dependencies -- [Node.JS 16+](https://nodejs.org/en/) +- [Node.JS 20.8+](https://nodejs.org/en/) - Recommended to manageNode.JS versions with [nvm](https://www.npmjs.com/package/npx) - [install](https://github.com/nvm-sh/nvm#install--update-script) - [Yarn - Classic](https://classic.yarnpkg.com/en/docs/getting-started) - [install](https://classic.yarnpkg.com/en/docs/install#windows-stable) @@ -78,8 +78,8 @@ yarn test:coverage #### Code Coverage -Code coverage configuration is managed in the `vite.config.js` and is generated -by [c8](https://github.com/bcoe/c8#readme). +Code coverage configuration is managed in the `vite.config.js`by [vitest configuration](https://vitest.dev/config/#coverage) +and is generated by [v8](https://v8.dev/blog/javascript-code-coverage#javascript-code-coverage-in-v8). Visit `<{root_project_path/rosalution/frontend/coverage/index.html}>` within the browser diff --git a/frontend/package.json b/frontend/package.json index c6fb019c..51b4f047 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,25 +15,25 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "6.2.1", - "@fortawesome/free-regular-svg-icons": "6.2.1", - "@fortawesome/free-solid-svg-icons": "6.2.1", - "@fortawesome/vue-fontawesome": "3.0.2", + "@fortawesome/fontawesome-svg-core": "6.4.2", + "@fortawesome/free-regular-svg-icons": "6.4.2", + "@fortawesome/free-solid-svg-icons": "6.4.2", + "@fortawesome/vue-fontawesome": "3.0.3", "@rollup/plugin-strip": "3.0.2", - "vue": "3.2.45", - "vue-router": "4.1.6" + "vue": "3.3.4", + "vue-router": "4.2.5" }, "devDependencies": { - "@vitejs/plugin-vue": "4.3.4", - "@vitest/coverage-c8": "0.27.2", - "@vitest/ui": "0.27.2", + "@vitejs/plugin-vue": "4.4.0", + "@vitest/coverage-v8": "0.34.6", + "@vitest/ui": "0.34.6", "@vue/test-utils": "2.4.1", - "eslint": "8.32.0", + "eslint": "8.50.0", "eslint-config-google": "0.14.0", - "eslint-plugin-vue": "9.9.0", - "happy-dom": "6.0.4", - "sinon": "15.0.1", - "vite": "4.4.9", - "vitest": "0.27.2" + "eslint-plugin-vue": "9.17.0", + "happy-dom": "12.8.0", + "sinon": "16.0.0", + "vite": "4.4.10", + "vitest": "0.34.6" } } diff --git a/frontend/src/components/AnnotationView/AllianceGenomeCard.vue b/frontend/src/components/AnnotationView/AllianceGenomeCard.vue index efa5a949..9ab93e01 100644 --- a/frontend/src/components/AnnotationView/AllianceGenomeCard.vue +++ b/frontend/src/components/AnnotationView/AllianceGenomeCard.vue @@ -48,7 +48,7 @@ diff --git a/frontend/src/components/AnnotationView/CardDataset.vue b/frontend/src/components/AnnotationView/CardDataset.vue index 4c687895..083ca902 100644 --- a/frontend/src/components/AnnotationView/CardDataset.vue +++ b/frontend/src/components/AnnotationView/CardDataset.vue @@ -12,7 +12,7 @@