Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/dataset-catalogue
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/public/locales/en/default_translation_en.json
#	src/public/locales/fr/default_translation_fr.json
  • Loading branch information
davidlougheed committed Nov 25, 2024
2 parents 8fbf828 + 68bb886 commit ae0a94e
Show file tree
Hide file tree
Showing 37 changed files with 844 additions and 545 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM node:20-bookworm-slim AS build
FROM --platform=$BUILDPLATFORM node:22-bookworm-slim AS build

# Build bento_public with NodeJS + Webpack
# - Use BUILDPLATFORM for running webpack, since it should perform a lot better.
Expand All @@ -24,13 +24,13 @@ RUN npm run build
FROM nginx:1.26

# Install node so that we can run the create_config_prod.js & create_service_info.js scripts
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get update -y && \
apt-get install -y ca-certificates curl gnupg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | \
tee /etc/apt/sources.list.d/nodesource.list && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
Expand Down
4 changes: 2 additions & 2 deletions dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM node:20-bookworm-slim AS install
FROM --platform=$BUILDPLATFORM node:22-bookworm-slim AS install

WORKDIR /bento-public

Expand All @@ -7,7 +7,7 @@ COPY package-lock.json .

RUN npm ci

FROM ghcr.io/bento-platform/bento_base_image:node-debian-2024.10.01
FROM ghcr.io/bento-platform/bento_base_image:node-debian-2024.11.01

LABEL org.opencontainers.image.description="Local development image for Bento Public."

Expand Down
661 changes: 328 additions & 333 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"main": "index.js",
"scripts": {
"build": "npx webpack --mode=production",
"build-dev": "rm -rf dist/* && npx webpack --mode=development",
"watch": "rm -rf dist/* && npx webpack --mode=development --watch",
"build-dev": "npx webpack --mode=development",
"watch": "npx webpack --mode=development --watch",
"start": "webpack serve --mode development --no-web-socket-server",
"lint": "npx eslint src/js",
"lint-fix": "npx eslint --fix src/js",
Expand All @@ -19,28 +19,29 @@
"dependencies": {
"@ant-design/icons": "^5.5.1",
"@reduxjs/toolkit": "^1.9.7",
"antd": "^5.21.5",
"antd": "^5.22.2",
"axios": "^1.7.7",
"bento-auth-js": "^6.0.2",
"bento-auth-js": "^7.0.0",
"bento-charts": "^2.12.0",
"dotenv": "^16.3.1",
"i18next": "^23.16.2",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"i18next-http-backend": "^2.7.1",
"leaflet": "^1.9.4",
"less": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^15.1.0",
"react-i18next": "^15.1.1",
"react-icons": "^5.0.1",
"react-leaflet": "^4.2.1",
"react-redux": "^8.1.3",
"react-router-dom": "^6.27.0",
"recharts": "^2.12.2",
"react-router-dom": "^6.28.0",
"recharts": "^2.13.3",
"redux": "^4.2.1",
"redux-thunk": "^2.4.1"
},
"devDependencies": {
"@types/json-schema": "^7.0.15",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
Expand All @@ -63,7 +64,7 @@
"style-loader": "^4.0.0",
"ts-loader": "^9.5.1",
"typescript": "~5.5.3",
"webpack": "^5.90.3",
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4",
"webpack-dev-middleware": "^7.0.0",
"webpack-dev-server": "^5.1.0",
Expand Down
10 changes: 2 additions & 8 deletions src/js/components/Beacon/BeaconCommon/AssemblyIdSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ import type { FormField, BeaconAssemblyIds } from '@/types/beacon';

const AssemblyIdSelect = ({ field, beaconAssemblyIds, disabled }: AssemblyIdSelectProps) => {
const t = useTranslationFn();
const assemblyIdOptions = beaconAssemblyIds.map((assembly) => (
<Select.Option key={assembly} value={assembly}>
{assembly}
</Select.Option>
));
const assemblyIdOptions = beaconAssemblyIds.map((assembly) => ({ value: assembly, label: assembly }));

return (
<Form.Item name={field.name} label={t(field.name)} rules={field.rules}>
<Select style={{ width: '100%' }} disabled={disabled}>
{assemblyIdOptions}
</Select>
<Select style={{ width: '100%' }} disabled={disabled} options={assemblyIdOptions} />
</Form.Item>
);
};
Expand Down
9 changes: 4 additions & 5 deletions src/js/components/Beacon/BeaconCommon/BeaconQueryFormUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ import {
BUTTON_STYLE,
CARD_STYLES,
} from '@/constants/beaconConstants';
import { T_PLURAL_COUNT } from '@/constants/i18n';

const STARTER_FILTER = { index: 1, active: true };
const VARIANTS_FORM_ERROR_MESSAGE =
'Variants form should include either an end position or both reference and alternate bases';

// TODOs
// example searches, either hardcoded or configurable
Expand Down Expand Up @@ -157,7 +156,7 @@ const BeaconQueryFormUi = ({
if (!variantsFormValid(formValues)) {
setHasFormError(true);
setErrorAlertClosed(false);
setFormErrorMessage(t(VARIANTS_FORM_ERROR_MESSAGE));
setFormErrorMessage(t('beacon.variants_form_error'));
return;
}

Expand Down Expand Up @@ -233,7 +232,7 @@ const BeaconQueryFormUi = ({
{hasVariants && (
<Col xs={24} lg={12}>
<Card
title={t('entities.Variants')}
title={t('entities.variant', T_PLURAL_COUNT)}
style={CARD_STYLE}
styles={CARD_STYLES}
extra={
Expand All @@ -242,7 +241,7 @@ const BeaconQueryFormUi = ({
</SearchToolTip>
}
>
<VariantsForm beaconAssemblyIds={beaconAssemblyIds} />
<VariantsForm isNetworkQuery={isNetworkQuery} beaconAssemblyIds={beaconAssemblyIds} />
</Card>
</Col>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
import { ToolTipText } from './ToolTipText';
import { useTranslationFn } from '@/hooks';
import { Space, Typography } from 'antd';
import { useTranslationFn } from '@/hooks';
import { range } from '@/utils/arrays';

const { Title } = Typography;

// complexity of instructions suggests the form isn't intuitive enough
const VARIANTS_INSTRUCTIONS_TITLE = 'Variant search';
const VARIANTS_INSTRUCTIONS_LINE1a =
'To search for all variants inside a range: fill both "Variant start" and "Variant end",';
const VARIANTS_INSTRUCTIONS_LINE1b =
'all variants inside the range will be returned. You can optionally filter by reference or alternate bases.';
import { ToolTipText } from './ToolTipText';

const VARIANTS_INSTRUCTIONS_LINE2a =
'To search for a variant at a particular position, either set "Variant end" to the same value in "Variant start",';
const VARIANTS_INSTRUCTIONS_LINE2b = 'or fill in values for both reference and alternate bases.';
const VARIANTS_INSTRUCTIONS_LINE3 = '"Chromosome", "Variant start" and "Assembly ID" are always required.';
const VARIANTS_INSTRUCTIONS_LINE4a = 'Coordinates are one-based.';
const VARIANTS_INSTRUCTIONS_LINE4b = 'Leave this form blank to search by metadata only.';
// complexity of instructions suggests the form isn't intuitive enough
const HELP_LINES = 4;

const VariantsInstructions = () => {
const t = useTranslationFn();
return (
<Space direction="vertical" style={{ minWidth: '510px' }}>
<Title level={4} style={{ color: 'white', marginTop: '10px' }}>
{VARIANTS_INSTRUCTIONS_TITLE}
{t('beacon.variants_help_title')}
</Title>
<ToolTipText>{t(VARIANTS_INSTRUCTIONS_LINE1a) + ' ' + t(VARIANTS_INSTRUCTIONS_LINE1b)}</ToolTipText>
<ToolTipText>{t(VARIANTS_INSTRUCTIONS_LINE2a) + ' ' + t(VARIANTS_INSTRUCTIONS_LINE2b)}</ToolTipText>
<ToolTipText>{t(VARIANTS_INSTRUCTIONS_LINE3)}</ToolTipText>
<ToolTipText>{t(VARIANTS_INSTRUCTIONS_LINE4a) + ' ' + t(VARIANTS_INSTRUCTIONS_LINE4b)}</ToolTipText>
{range(HELP_LINES).map((x) => (
<ToolTipText key={x}>{t(`beacon.variants_help_${x + 1}`, { joinArrays: ' ' })}</ToolTipText>
))}
</Space>
);
};
Expand Down
24 changes: 19 additions & 5 deletions src/js/components/Beacon/BeaconCommon/VariantInput.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { Form, Input } from 'antd';
import { Form, Input, Select } from 'antd';
import type { DefaultOptionType } from 'antd/es/select/index';
import { useTranslationFn } from '@/hooks';
import type { FormField } from '@/types/beacon';

const VariantInput = ({ field, disabled }: VariantInputProps) => {
type InputMode = { type: 'input' } | { type: 'select'; options?: DefaultOptionType[] };

const VariantInput = ({ field, disabled, mode }: VariantInputProps) => {
const t = useTranslationFn();
return (
<div>
<>
<Form.Item name={field.name} label={t(field.name)} rules={field.rules}>
<Input placeholder={field.placeholder} disabled={disabled} />
{!mode || mode.type === 'input' ? (
<Input placeholder={field.placeholder} disabled={disabled} />
) : (
<Select
placeholder={field.placeholder}
disabled={disabled}
options={mode.options}
showSearch={true}
optionFilterProp="value"
/>
)}
</Form.Item>
</div>
</>
);
};

export interface VariantInputProps {
field: FormField;
disabled: boolean;
mode?: InputMode;
}

export default VariantInput;
94 changes: 80 additions & 14 deletions src/js/components/Beacon/BeaconCommon/VariantsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,60 @@
import type { CSSProperties } from 'react';
import { Col, Row } from 'antd';
import VariantInput from './VariantInput';
import AssemblyIdSelect from './AssemblyIdSelect';
import { type CSSProperties, useEffect, useMemo } from 'react';

import { Col, Form, Row } from 'antd';
import type { DefaultOptionType } from 'antd/es/select/index';

import { useTranslationFn } from '@/hooks';
import { useReference } from '@/features/reference/hooks';
import type { Contig } from '@/features/reference/types';
import type { BeaconAssemblyIds } from '@/types/beacon';

import VariantInput from './VariantInput';
import AssemblyIdSelect from './AssemblyIdSelect';

type ContigOptionType = DefaultOptionType & { value: string };

// form state has to be one of these:
// empty (except for autofilled assemblyID)
// chrom, start, assemblyID, end
// chrom, start, assemblyID, ref, alt

// forgiving chromosome regex
// accepts X, Y, etc. and any one- or two-digit non-zero number
// note that, eg, polar bears have 37 pairs of chromosomes...
const CHROMOSOME_REGEX = /^([1-9][0-9]?|X|x|Y|y|M|m|MT|mt)$/;

const NUCLEOTIDES_REGEX = /^([acgtnACGTN])*$/;
const DIGITS_REGEX = /^[0-9]+$/;
const DIGITS_REGEX = /^\d+$/;

const HUMAN_LIKE_CONTIG_REGEX = /^(?:chr)?(\d+|X|Y|M)$/;
const HUMAN_LIKE_EXCLUDE_CONTIG_REGEX = /^(?:chr)?(\d+|X|Y|M|Un)_.+$/;

const contigToOption = (c: Contig): ContigOptionType => ({ value: c.name });

const contigOptionSort = (a: ContigOptionType, b: ContigOptionType) => {
const aMatch = a.value.match(HUMAN_LIKE_CONTIG_REGEX);
const bMatch = b.value.match(HUMAN_LIKE_CONTIG_REGEX);
if (aMatch) {
if (bMatch) {
const aNoPrefix = aMatch[1];
const bNoPrefix = bMatch[1];
const aNumeric = !!aNoPrefix.match(DIGITS_REGEX);
const bNumeric = !!bNoPrefix.match(DIGITS_REGEX);
if (aNumeric) {
if (bNumeric) {
return parseInt(aNoPrefix, 10) - parseInt(bNoPrefix, 10);
} else {
return -1;
}
} else if (bNumeric) {
return 1;
} else {
return aNoPrefix.localeCompare(bNoPrefix);
}
} else {
// chr## type contigs put before other types
return -1;
}
}
return a.value.localeCompare(b.value);
};

const filterOutHumanLikeExtraContigs = (opt: ContigOptionType) => !opt.value.match(HUMAN_LIKE_EXCLUDE_CONTIG_REGEX);

const FORM_STYLE: CSSProperties = {
display: 'flex',
Expand All @@ -25,13 +63,36 @@ const FORM_STYLE: CSSProperties = {

const FORM_ROW_GUTTER: [number, number] = [12, 0];

const VariantsForm = ({ beaconAssemblyIds }: VariantsFormProps) => {
const VariantsForm = ({ isNetworkQuery, beaconAssemblyIds }: VariantsFormProps) => {
const { genomesByID } = useReference();

// Pick up form context from outside
const form = Form.useFormInstance();
const currentAssemblyID = Form.useWatch('Assembly ID', form);

// Right now, we cannot figure out the contig options for the network, so we fall back to a normal input box.
const availableContigs = useMemo<ContigOptionType[]>(
() =>
!isNetworkQuery && currentAssemblyID && genomesByID[currentAssemblyID]
? genomesByID[currentAssemblyID].contigs
.map(contigToOption)
.sort(contigOptionSort)
.filter(filterOutHumanLikeExtraContigs)
: [],
[isNetworkQuery, currentAssemblyID, genomesByID]
);
const assemblySelect = !!availableContigs.length;

useEffect(() => {
// Clear contig value when list of available contigs changes:
form.setFieldValue('Chromosome', '');
}, [form, availableContigs]);

const t = useTranslationFn();
const formFields = {
referenceName: {
name: 'Chromosome',
rules: [{ pattern: CHROMOSOME_REGEX, message: t('Enter a chromosome name, e.g.: "17" or "X"') }],
placeholder: '1-22, X, Y, M',
placeholder: !currentAssemblyID ? t('beacon.select_asm') : '',
initialValue: '',
},
start: {
Expand Down Expand Up @@ -67,7 +128,11 @@ const VariantsForm = ({ beaconAssemblyIds }: VariantsFormProps) => {
<div style={FORM_STYLE}>
<Row gutter={FORM_ROW_GUTTER}>
<Col span={8}>
<VariantInput field={formFields.referenceName} disabled={variantsError} />
<VariantInput
field={formFields.referenceName}
disabled={variantsError || !currentAssemblyID}
mode={assemblySelect ? { type: 'select', options: availableContigs } : { type: 'input' }}
/>
</Col>
<Col span={8}>
<VariantInput field={formFields.start} disabled={variantsError} />
Expand All @@ -94,6 +159,7 @@ const VariantsForm = ({ beaconAssemblyIds }: VariantsFormProps) => {
};

export interface VariantsFormProps {
isNetworkQuery?: boolean;
beaconAssemblyIds: BeaconAssemblyIds;
}

Expand Down
8 changes: 4 additions & 4 deletions src/js/components/BentoAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fetchGohanData, fetchKatsuData } from '@/features/ingestion/lastIngesti
import { makeGetDataTypes } from '@/features/dataTypes/dataTypes.store';
import { useMetadata } from '@/features/metadata/hooks';
import { getProjects, markScopeSet, selectScope } from '@/features/metadata/metadata.store';
import { getGenomes } from '@/features/reference/reference.store';

import Loader from '@/components/Loader';
import DefaultLayout from '@/components/Util/DefaultLayout';
Expand Down Expand Up @@ -106,10 +107,9 @@ const BentoAppRouter = () => {
dispatch(makeGetAboutRequest());
dispatch(fetchGohanData());
dispatch(makeGetServiceInfoRequest());
if (isAuthenticated) {
dispatch(makeGetDataTypes());
}
}, [dispatch, isAuthenticated]);
dispatch(makeGetDataTypes());
dispatch(getGenomes());
}, [dispatch]);

if (isAutoAuthenticating || projectsStatus === RequestStatus.Pending) {
return <Loader />;
Expand Down
Loading

0 comments on commit ae0a94e

Please sign in to comment.