From a4450ee553e2a590265211f9dd8c788d57e79ded Mon Sep 17 00:00:00 2001 From: Sabrina Yan <9669990+violetbrina@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:30:47 +1000 Subject: [PATCH] Uplift code quality in metamist repo (#957) * Add tests directory to sonarqube config file * Hide db backup folder results * Fix test exclusion for code coverage settings * Address all current metamist bugs * Fix security issues, including exclusive use of formatMoney with resistent regex * formatMoney bug fix * Fix the ValueFilter so that the useState is called in the same order in every render * Change so that the nonroot user is used in the db dockerfile * Change so that the nonroot user is used in the api dockerfile * Fix default logging level to warning to reduce the leak of secure information * Add merge_group to the test workflow * Fix web pages format using prettier * Fix web pages format using prettier - npm ci ran first * Add referenceBranch to sonar-project.properties * Update test workflow to include metamist dev branch version in analysis * Add Sonarqube scan to deploy check, update db Dockerfile based on comments * Fix run fastapi command in api Dockerfile --- .github/workflows/deploy.yaml | 147 ++++++++++++++++++ .github/workflows/test.yaml | 5 +- .gitignore | 2 + db/deploy/Dockerfile | 18 ++- db/python/connect.py | 2 +- db/python/gcp_connect.py | 2 +- deploy/api/Dockerfile | 21 ++- metamist/audit/audit_upload_bucket.py | 2 +- .../audit/delete_assay_files_from_audit.py | 2 +- metamist/parser/generic_parser.py | 3 +- sonar-project.properties | 4 + web/src/App.tsx | 2 +- web/src/index.css | 2 +- .../pages/billing/BillingCostByAnalysis.tsx | 6 +- .../pages/billing/BillingCostByCategory.tsx | 2 +- web/src/pages/billing/BillingCostByMonth.tsx | 2 +- web/src/pages/billing/BillingCostByTime.tsx | 2 +- web/src/pages/billing/BillingHome.tsx | 2 +- .../pages/billing/BillingInvoiceMonthCost.tsx | 19 +-- .../components/BillingCostByTimeTable.tsx | 13 +- web/src/pages/comments/CommentHistory.tsx | 2 +- .../comments/entities/FamilyCommentsView.tsx | 2 +- .../entities/ParticipantCommentsView.tsx | 2 +- .../comments/entities/ProjectCommentsView.tsx | 2 +- .../comments/entities/SampleCommentsView.tsx | 2 +- .../entities/SequencingGroupCommentsView.tsx | 2 +- web/src/pages/project/ValueFilter.tsx | 25 ++- web/src/shared/components/Header/NavBar.css | 1 - web/src/shared/components/SeqPanel.tsx | 2 +- .../components/pedigree/TangledTree.tsx | 2 +- web/src/shared/utilities/formatMoney.ts | 4 +- 31 files changed, 228 insertions(+), 76 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 0c69fdcbf..b96a05646 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -12,8 +12,155 @@ permissions: contents: read jobs: + unittests: + name: Run unit tests + # Run on merge to main, where the commit name starts with "Bump version:" (for bump2version) + # if: "startsWith(github.event.head_commit.message, 'Bump version:')" + runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: 1 + BUILDKIT_PROGRESS: plain + CLOUDSDK_CORE_DISABLE_PROMPTS: 1 + # used for generating API + SM_DOCKER: samplemetadata:dev + defaults: + run: + shell: bash -eo pipefail -l {0} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + + - name: Setup build env + run: | + set -euxo pipefail + + pip install --no-deps -r requirements-dev.txt + + # openapi-generator + wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.3.0/openapi-generator-cli-5.3.0.jar -O openapi-generator-cli.jar + + # liquibase connector + pushd db/ + wget https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar + popd + # liquibase + VERSION=4.28.0 + curl -L https://github.com/liquibase/liquibase/releases/download/v${VERSION}/liquibase-${VERSION}.zip --output liquibase-${VERSION}.zip + unzip -o -d liquibase liquibase-${VERSION}.zip + echo "$(pwd)/liquibase" >> $GITHUB_PATH + + - name: 'build image' + run: | + docker build \ + --build-arg SM_ENVIRONMENT=local \ + --tag $SM_DOCKER \ + -f deploy/api/Dockerfile \ + . + + - name: 'build deployable API' + run: | + export OPENAPI_COMMAND="java -jar openapi-generator-cli.jar" + python regenerate_api.py + pip install . + + - name: 'Run unit tests' + id: runtests + run: | + coverage run -m pytest --doctest-modules --doctest-continue-on-failure test/ --junitxml=test-execution.xml + rc=$? + coverage xml + + echo "rc=$rc" >> $GITHUB_OUTPUT + + - name: 'Upload coverage report' + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + + - name: 'Save coverage report as an Artifact' + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: ./coverage.xml + + - name: 'Save execution report as an Artifact' + uses: actions/upload-artifact@v4 + with: + name: execution-report + path: ./test-execution.xml + + - name: 'build web front-end' + run: | + set -eo pipefail + pushd web + # installs package-lock, not what it thinks it should be + npm ci + npm run build + rc=$? + + echo "web_rc=$rc" >> $GITHUB_OUTPUT + # eventually run web front-end tests + popd + + - name: Fail if unit tests are not passing + if: ${{ steps.runtests.outputs.rc != 0}} + uses: actions/github-script@v6 + with: + script: | + core.setFailed('Unittests failed with rc = ${{ steps.runtests.outputs.rc }}') + + - name: Fail if web build fails + if: ${{ steps.runtests.outputs.rc != 0}} + uses: actions/github-script@v6 + with: + script: | + core.setFailed('Web failed to build with rc = ${{ steps.runtests.outputs.web_rc }}') + + sonarqube: + name: SonarQube scan + runs-on: ubuntu-latest + needs: unittests + environment: production + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + # Download the coverage report artifact + - name: 'Download coverage and execution report' + uses: actions/download-artifact@v4 + with: + pattern: '*-report' + + # Perform the SonarQube scan + - uses: sonarsource/sonarqube-scan-action@master + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + + # Optional: Fail the job if Quality Gate is red + # If you wish to fail your job when the Quality Gate is red, uncomment the + # following lines. This would typically be used to fail a deployment. + # - uses: sonarsource/sonarqube-quality-gate-action@master + # timeout-minutes: 5 + # env: + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + deploy: + name: Deploy runs-on: ubuntu-latest + needs: sonarqube environment: production env: DOCKER_BUILDKIT: 1 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index efa6b791d..9651715dc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,11 +1,10 @@ name: Test -on: push +on: + push: jobs: unittests: name: Run unit tests - # Run on merge to main, where the commit name starts with "Bump version:" (for bump2version) - # if: "startsWith(github.event.head_commit.message, 'Bump version:')" runs-on: ubuntu-latest env: DOCKER_BUILDKIT: 1 diff --git a/.gitignore b/.gitignore index 1d5308de5..2382ebfd9 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ profiles # sonarqube .scannerwork/ + +latest_backup/ diff --git a/db/deploy/Dockerfile b/db/deploy/Dockerfile index 24facb0f3..35072a678 100644 --- a/db/deploy/Dockerfile +++ b/db/deploy/Dockerfile @@ -8,17 +8,31 @@ ENV LIQUIBASE_VERSION=4.26.0 ENV MARIADB_JDBC_VERSION=3.0.3 # Install system dependencies -RUN apt-get update && apt-get install -y --no-install-recommends gcc git ssh default-jdk wget unzip +RUN apt-get update && apt-get install -y --no-install-recommends gcc git ssh default-jdk wget unzip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create a non-root user and group +RUN groupadd -r appuser && useradd -r -g appuser -d /home/appuser -m -s /bin/bash appuser # Download and install Liquibase RUN wget https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.zip \ && unzip liquibase-${LIQUIBASE_VERSION}.zip -d /opt/liquibase \ && chmod +x /opt/liquibase/liquibase \ - # Clean up to reduce layer size && rm liquibase-${LIQUIBASE_VERSION}.zip \ + # Clean up to reduce layer size && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Change ownership of the liquibase directory to the non-root user +RUN chown -R appuser:appuser /opt/liquibase + +# Switch to the non-root user +USER appuser + +# Set the working directory +WORKDIR /home/appuser + # Download the MariaDB JDBC driver RUN wget https://downloads.mariadb.com/Connectors/java/connector-java-${MARIADB_JDBC_VERSION}/mariadb-java-client-${MARIADB_JDBC_VERSION}.jar RUN mv mariadb-java-client-${MARIADB_JDBC_VERSION}.jar /opt/ diff --git a/db/python/connect.py b/db/python/connect.py index 2d51b5e8f..fd9e97e08 100644 --- a/db/python/connect.py +++ b/db/python/connect.py @@ -23,7 +23,7 @@ ) from models.models.project import Project, ProjectId, ProjectMemberRole -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) TABLES_ORDERED_BY_FK_DEPS = [ diff --git a/db/python/gcp_connect.py b/db/python/gcp_connect.py index 5d0271e68..fecfc913b 100644 --- a/db/python/gcp_connect.py +++ b/db/python/gcp_connect.py @@ -10,7 +10,7 @@ from db.python.utils import InternalError -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) diff --git a/deploy/api/Dockerfile b/deploy/api/Dockerfile index da0ab23b3..a4e43553f 100644 --- a/deploy/api/Dockerfile +++ b/deploy/api/Dockerfile @@ -7,15 +7,26 @@ ENV SM_ENVIRONMENT ${SM_ENVIRONMENT} # Allow statements and log messages to immediately appear in the Knative logs. ENV PYTHONUNBUFFERED 1 -EXPOSE $PORT +# Create a non-root user and group +RUN groupadd -r appuser && useradd -r -g appuser -d /home/appuser -m -s /bin/bash appuser +# Set the working directory WORKDIR /app/sample_metadata/ -COPY requirements.txt requirements.txt +# Copy requirements file and install dependencies +COPY requirements.txt requirements.txt RUN pip install --no-cache-dir --no-deps -r requirements.txt +# Copy the application code COPY api api -COPY db db/ -COPY models models/ -CMD uvicorn --port ${PORT} --host 0.0.0.0 api.server:app +# Change ownership of the application directory to the non-root user +RUN chown -R appuser:appuser /app/sample_metadata/ + +# Switch to the non-root user +USER appuser + +EXPOSE $PORT + +# Command to run the FastAPI app +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "$PORT"] diff --git a/metamist/audit/audit_upload_bucket.py b/metamist/audit/audit_upload_bucket.py index 22827a532..c58e006ee 100644 --- a/metamist/audit/audit_upload_bucket.py +++ b/metamist/audit/audit_upload_bucket.py @@ -333,7 +333,7 @@ def main( if __name__ == '__main__': logging.basicConfig( - level=logging.INFO, + level=logging.WARNING, format='%(asctime)s %(levelname)s %(module)s:%(lineno)d - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', stream=sys.stderr, diff --git a/metamist/audit/delete_assay_files_from_audit.py b/metamist/audit/delete_assay_files_from_audit.py index c60e42439..5aedd2e55 100644 --- a/metamist/audit/delete_assay_files_from_audit.py +++ b/metamist/audit/delete_assay_files_from_audit.py @@ -86,7 +86,7 @@ def main(delete_field_name, delete_file_path): if __name__ == '__main__': logging.basicConfig( - level=logging.INFO, + level=logging.WARNING, format='%(asctime)s %(levelname)s %(module)s:%(lineno)d - %(message)s', datefmt='%Y-%M-%d %H:%M:%S', stream=sys.stderr, diff --git a/metamist/parser/generic_parser.py b/metamist/parser/generic_parser.py index 0aece2af4..db3576c9c 100644 --- a/metamist/parser/generic_parser.py +++ b/metamist/parser/generic_parser.py @@ -43,9 +43,8 @@ else: from typing_extensions import Literal -logging.basicConfig() +logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) PRIMARY_EXTERNAL_ORG = '' diff --git a/sonar-project.properties b/sonar-project.properties index 576979e29..0719cfb89 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,3 +2,7 @@ sonar.projectKey=populationgenomics_metamist sonar.python.version=3.11 sonar.python.coverage.reportPaths=coverage.xml sonar.python.xunit.reportPath=test-execution.xml +sonar.sources=. +sonar.exclusions=test/**,*.xml +sonar.tests=test/ +sonar.cpd.cache.enabled=true diff --git a/web/src/App.tsx b/web/src/App.tsx index 4fa16bb47..65676e7b9 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { BrowserRouter as Router } from 'react-router-dom' import Routes from './Routes' import NavBar from './shared/components/Header/NavBar' -import { ViewerContext, useViewer } from './viewer' +import { useViewer, ViewerContext } from './viewer' const App: React.FunctionComponent = () => { const { viewer, error } = useViewer() diff --git a/web/src/index.css b/web/src/index.css index 3e01b622c..016e5d9b9 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -151,7 +151,6 @@ body { background-color: var(--color-bg); color: var(--color-text-primary); - background: var(--color-bg); } code { @@ -203,6 +202,7 @@ blockquote { .ui .modal > .content { background: var(--color-bg-card); } + .ui .modal > .header { background: var(--color-bg-card); color: var(--color-text-primary); diff --git a/web/src/pages/billing/BillingCostByAnalysis.tsx b/web/src/pages/billing/BillingCostByAnalysis.tsx index a225b4bde..b3e4f9538 100644 --- a/web/src/pages/billing/BillingCostByAnalysis.tsx +++ b/web/src/pages/billing/BillingCostByAnalysis.tsx @@ -5,7 +5,6 @@ import { Button, Card, Dropdown, Grid, Input, Message } from 'semantic-ui-react' import { PaddedPage } from '../../shared/components/Layout/PaddedPage' import LoadingDucks from '../../shared/components/LoadingDucks/LoadingDucks' import generateUrl from '../../shared/utilities/generateUrl' -import { getMonthStartDate } from '../../shared/utilities/monthStartEndDate' import { AnalysisCostRecord, BillingApi } from '../../sm-api' import BatchGrid from './components/BatchGrid' @@ -22,9 +21,6 @@ const BillingCostByAnalysis: React.FunctionComponent = () => { const [isLoading, setIsLoading] = React.useState(true) const [error, setError] = React.useState() const [data, setData] = React.useState() - const [start, setStart] = React.useState( - searchParams.get('start') ?? getMonthStartDate() - ) const setBillingRecord = (records: AnalysisCostRecord[]) => { setIsLoading(false) @@ -138,7 +134,7 @@ const BillingCostByAnalysis: React.FunctionComponent = () => { setError(undefined)}> {error}
-
diff --git a/web/src/pages/billing/BillingCostByCategory.tsx b/web/src/pages/billing/BillingCostByCategory.tsx index fce19ca54..629db7422 100644 --- a/web/src/pages/billing/BillingCostByCategory.tsx +++ b/web/src/pages/billing/BillingCostByCategory.tsx @@ -191,7 +191,7 @@ const BillingCostByCategory: React.FunctionComponent = () => { setError(undefined)}> {error}
-
diff --git a/web/src/pages/billing/BillingCostByMonth.tsx b/web/src/pages/billing/BillingCostByMonth.tsx index 40dc076b7..3c69e616d 100644 --- a/web/src/pages/billing/BillingCostByMonth.tsx +++ b/web/src/pages/billing/BillingCostByMonth.tsx @@ -150,7 +150,7 @@ const BillingCostByTime: React.FunctionComponent = () => { setError(undefined)}> {error}
-
diff --git a/web/src/pages/billing/BillingCostByTime.tsx b/web/src/pages/billing/BillingCostByTime.tsx index 70f476022..ee59c8289 100644 --- a/web/src/pages/billing/BillingCostByTime.tsx +++ b/web/src/pages/billing/BillingCostByTime.tsx @@ -185,7 +185,7 @@ const BillingCostByTime: React.FunctionComponent = () => { setError(undefined)}> {error}
-
diff --git a/web/src/pages/billing/BillingHome.tsx b/web/src/pages/billing/BillingHome.tsx index 4cb827ea9..706b45e2a 100644 --- a/web/src/pages/billing/BillingHome.tsx +++ b/web/src/pages/billing/BillingHome.tsx @@ -3,7 +3,7 @@ import ReactGoogleSlides from 'react-google-slides' import { Button, Menu, MenuItem, Segment, SemanticWIDTHS } from 'semantic-ui-react' import { PaddedPage } from '../../shared/components/Layout/PaddedPage' import { ThemeContext } from '../../shared/components/ThemeProvider' -import { IBillingPage, billingPages } from './BillingPages' +import { billingPages, IBillingPage } from './BillingPages' // Google Slides const Slides = React.memo(function ReactGoogleSlidesWrapper({ link }: { link: string }) { diff --git a/web/src/pages/billing/BillingInvoiceMonthCost.tsx b/web/src/pages/billing/BillingInvoiceMonthCost.tsx index c9366c14b..c8710c489 100644 --- a/web/src/pages/billing/BillingInvoiceMonthCost.tsx +++ b/web/src/pages/billing/BillingInvoiceMonthCost.tsx @@ -13,6 +13,7 @@ import { HorizontalStackedBarChart } from '../../shared/components/Graphs/Horizo import { PaddedPage } from '../../shared/components/Layout/PaddedPage' import Table from '../../shared/components/Table' import { convertFieldName } from '../../shared/utilities/fieldName' +import formatMoney from '../../shared/utilities/formatMoney' import generateUrl from '../../shared/utilities/generateUrl' import { BillingApi, BillingColumn, BillingCostBudgetRecord } from '../../sm-api' import FieldSelector from './components/FieldSelector' @@ -129,14 +130,6 @@ const BillingCurrentCost = () => { } } - function currencyFormat(num: number | undefined | null): string { - if (num === undefined || num === null) { - return '' - } - - return `$${num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')}` - } - function percFormat(num: number | undefined | null): string { if (num === undefined || num === null) { return '' @@ -414,7 +407,7 @@ const BillingCurrentCost = () => { { // @ts-ignore - currencyFormat(p[k.category]) + formatMoney(p[k.category]) } ) @@ -456,14 +449,14 @@ const BillingCurrentCost = () => { {invoiceMonth === thisMonth ? ( - {currencyFormat(dk.daily_cost)} + {formatMoney(dk.daily_cost)} ) : null} - {currencyFormat(dk.monthly_cost)} + {formatMoney(dk.monthly_cost)} @@ -473,14 +466,14 @@ const BillingCurrentCost = () => { {invoiceMonth === thisMonth ? ( - {currencyFormat(dk.daily_cost)} + {formatMoney(dk.daily_cost)} ) : null} - {currencyFormat(dk.monthly_cost)} + {formatMoney(dk.monthly_cost)} diff --git a/web/src/pages/billing/components/BillingCostByTimeTable.tsx b/web/src/pages/billing/components/BillingCostByTimeTable.tsx index e367b8c7c..8bf166277 100644 --- a/web/src/pages/billing/components/BillingCostByTimeTable.tsx +++ b/web/src/pages/billing/components/BillingCostByTimeTable.tsx @@ -4,6 +4,7 @@ import { IStackedAreaByDateChartData } from '../../../shared/components/Graphs/S import LoadingDucks from '../../../shared/components/LoadingDucks/LoadingDucks' import Table from '../../../shared/components/Table' import { convertFieldName } from '../../../shared/utilities/fieldName' +import formatMoney from '../../../shared/utilities/formatMoney' interface IBillingCostByTimeTableProps { heading: string @@ -105,14 +106,6 @@ const BillingCostByTimeTable: React.FC = ({ return undefined } - const currencyFormat = (num: number): string => { - if (num === undefined || num === null) { - return '' - } - - return `$${num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')}` - } - if (isLoading) { return (
@@ -160,7 +153,7 @@ const BillingCostByTimeTable: React.FC = ({ {headerFields().map((k) => ( - {currencyFormat(p.values[k.category])} + {formatMoney(p.values[k.category])} ))} @@ -224,7 +217,7 @@ const BillingCostByTimeTable: React.FC = ({ {headerFields().map((k) => ( - {currencyFormat( + {formatMoney( internalData.reduce( (acc, cur) => acc + cur.values[k.category], 0 diff --git a/web/src/pages/comments/CommentHistory.tsx b/web/src/pages/comments/CommentHistory.tsx index 6e4854a16..f59ec0b08 100644 --- a/web/src/pages/comments/CommentHistory.tsx +++ b/web/src/pages/comments/CommentHistory.tsx @@ -1,8 +1,8 @@ import { Box, List, ListItem, ListItemButton, ListItemText, Typography } from '@mui/material' import { DateTime } from 'luxon' import { useState } from 'react' -import { CommentContent } from './CommentContent' import { CommentData } from './commentConfig' +import { CommentContent } from './CommentContent' import { parseAuthor } from './commentUtils' export function CommentHistory(props: { comment: CommentData; theme: 'light' | 'dark' }) { diff --git a/web/src/pages/comments/entities/FamilyCommentsView.tsx b/web/src/pages/comments/entities/FamilyCommentsView.tsx index 6ec2870ef..03e8969ea 100644 --- a/web/src/pages/comments/entities/FamilyCommentsView.tsx +++ b/web/src/pages/comments/entities/FamilyCommentsView.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client' import { gql } from '../../../__generated__' -import { DiscussionView } from '../DiscussionView' import { useNewComment } from '../data/commentMutations' +import { DiscussionView } from '../DiscussionView' export const FAMILY_COMMENTS = gql(` query FamilyComments($familyId: Int!) { diff --git a/web/src/pages/comments/entities/ParticipantCommentsView.tsx b/web/src/pages/comments/entities/ParticipantCommentsView.tsx index 4468c9631..9c3453feb 100644 --- a/web/src/pages/comments/entities/ParticipantCommentsView.tsx +++ b/web/src/pages/comments/entities/ParticipantCommentsView.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client' import { gql } from '../../../__generated__' -import { DiscussionView } from '../DiscussionView' import { useNewComment } from '../data/commentMutations' +import { DiscussionView } from '../DiscussionView' export const PARTICIPANT_COMMENTS = gql(` query ParticipantComments($participantId: Int!) { diff --git a/web/src/pages/comments/entities/ProjectCommentsView.tsx b/web/src/pages/comments/entities/ProjectCommentsView.tsx index 64d28980f..ce4446591 100644 --- a/web/src/pages/comments/entities/ProjectCommentsView.tsx +++ b/web/src/pages/comments/entities/ProjectCommentsView.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client' import { gql } from '../../../__generated__' -import { DiscussionView } from '../DiscussionView' import { useNewComment } from '../data/commentMutations' +import { DiscussionView } from '../DiscussionView' export const PROJECT_COMMENTS = gql(` query ProjectComments($projectName: String!) { diff --git a/web/src/pages/comments/entities/SampleCommentsView.tsx b/web/src/pages/comments/entities/SampleCommentsView.tsx index 43534fb97..7659bd73c 100644 --- a/web/src/pages/comments/entities/SampleCommentsView.tsx +++ b/web/src/pages/comments/entities/SampleCommentsView.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client' import { gql } from '../../../__generated__' -import { DiscussionView } from '../DiscussionView' import { useNewComment } from '../data/commentMutations' +import { DiscussionView } from '../DiscussionView' export const SAMPLE_COMMENTS = gql(` query SampleComments($sampleId: String!) { diff --git a/web/src/pages/comments/entities/SequencingGroupCommentsView.tsx b/web/src/pages/comments/entities/SequencingGroupCommentsView.tsx index 000cb98b9..1e0d35e9a 100644 --- a/web/src/pages/comments/entities/SequencingGroupCommentsView.tsx +++ b/web/src/pages/comments/entities/SequencingGroupCommentsView.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client' import { gql } from '../../../__generated__' -import { DiscussionView } from '../DiscussionView' import { useNewComment } from '../data/commentMutations' +import { DiscussionView } from '../DiscussionView' export const SEQUENCING_GROUP_COMMENTS = gql(` query SequencingGroupComments($sequencingGroupId: String!) { diff --git a/web/src/pages/project/ValueFilter.tsx b/web/src/pages/project/ValueFilter.tsx index c213164aa..3f8302c8f 100644 --- a/web/src/pages/project/ValueFilter.tsx +++ b/web/src/pages/project/ValueFilter.tsx @@ -85,19 +85,22 @@ export const ValueFilter: React.FC = ({ // So check if the filterKey starts with 'meta.' to determine if it is a meta key, and // then check the [category].meta object for the value - if (!field.filter_key) return <> - - /* eslint-disable react-hooks/rules-of-hooks*/ const [_defaultFilterType, setDefaultFilterType] = React.useState< ProjectParticipantGridFilterType | undefined >() - /* eslint-enable react-hooks/rules-of-hooks*/ + + let optionsToCheck = props?.filterValues?.[category] || {} + const name = (field.filter_key ?? '').replace(/^meta\./, '') + + // @ts-ignore + const _value = optionsToCheck?.[name]?.[operator] + const [_tempValue, setTempValue] = React.useState(_value ?? '') + const tempValue = _tempValue ?? _value + + if (!field.filter_key) return <> const isMeta = field.filter_key?.startsWith('meta.') // set name to the filterKey without the .meta prefix - const name = field.filter_key.replace(/^meta\./, '') - - let optionsToCheck = props?.filterValues?.[category] || {} if (isMeta) { // get the meta bit from the filterValues @@ -143,14 +146,6 @@ export const ValueFilter: React.FC = ({ operator = getOperatorFromFilterType(queryType) } - // @ts-ignore - const _value = optionsToCheck?.[name]?.[operator] - - /* eslint-disable react-hooks/rules-of-hooks*/ - const [_tempValue, setTempValue] = React.useState(_value ?? '') - /* eslint-enable react-hooks/rules-of-hooks*/ - const tempValue = _tempValue ?? _value - const updateQueryType = (newFilterType: ProjectParticipantGridFilterType) => { setDefaultFilterType(newFilterType) const newOperator = getOperatorFromFilterType(newFilterType) diff --git a/web/src/shared/components/Header/NavBar.css b/web/src/shared/components/Header/NavBar.css index 3dd6f94e4..b26be7c5d 100644 --- a/web/src/shared/components/Header/NavBar.css +++ b/web/src/shared/components/Header/NavBar.css @@ -80,7 +80,6 @@ border-radius: 4px !important; padding: 10px !important; margin: 1px !important; - border-radius: 4px !important; font-weight: 400 !important; font-size: 1rem !important; font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important; diff --git a/web/src/shared/components/SeqPanel.tsx b/web/src/shared/components/SeqPanel.tsx index 5de78dca9..983b425e1 100644 --- a/web/src/shared/components/SeqPanel.tsx +++ b/web/src/shared/components/SeqPanel.tsx @@ -5,8 +5,8 @@ import { Accordion } from 'semantic-ui-react' import { GraphQlSequencingGroup } from '../../__generated__/graphql' import iconStyle from '../iconStyle' import { DeepPartial } from '../utilities/deepPartial' -import SequencingGroupInfo from './SequencingGroupInfo' import SequencingGroupLink from './links/SequencingGroupLink' +import SequencingGroupInfo from './SequencingGroupInfo' const SeqPanel: React.FunctionComponent<{ sequencingGroups: DeepPartial[] diff --git a/web/src/shared/components/pedigree/TangledTree.tsx b/web/src/shared/components/pedigree/TangledTree.tsx index 3c61219b5..da1074055 100644 --- a/web/src/shared/components/pedigree/TangledTree.tsx +++ b/web/src/shared/components/pedigree/TangledTree.tsx @@ -507,7 +507,7 @@ export const PersonNode: React.FC = ({ > - val ? `$${val.toFixed(dp).replace(/\d(?=(\d{3})+\.)/g, '$&,')}` : '' +const formatMoney = (val: number | undefined | null, dp: number = 2): string => + val ? `$${val.toFixed(dp).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}` : '' export default formatMoney