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/js/components/Overview/PublicOverview.tsx
#	src/public/locales/en/default_translation_en.json
#	src/public/locales/fr/default_translation_fr.json
  • Loading branch information
davidlougheed committed Dec 16, 2024
2 parents 341e8e1 + 46ef260 commit 1869850
Show file tree
Hide file tree
Showing 28 changed files with 224 additions and 131 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ it directly.
## Prerequisites:
- Node Package Manager

## Translations in dev mode
## Development

### Adding a new environment configuration variable

Any new environment / configuration variable must be registered in several places:

1. [`./create_config_prod.js`](./create_config_prod.js): mapping the environment variable to a config object entry for
production.
2. [`./webpack.config.js`](./webpack.config.js): setting a default value for the environment variable; used in Webpack development
builds.
3. [`./src/public/config.js`](./src/public/config.js): creating the shape of the global config object (using the config object entry key,
mapped to in 1.)
4. [`./src/js/config.ts`](./src/js/config.ts): loading from the global config object (production) via key or from the
environment variable directly, through Webpack replacement (development).

### Translations in dev mode
Add your English to French translations in `dist/public/locales/fr/translation_fr.json` for them to appear on the
website.
1 change: 1 addition & 0 deletions create_config_prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const siteConfig = {
TRANSLATED: parseBoolean(process.env.BENTO_PUBLIC_TRANSLATED),
BEACON_URL: process.env.BEACON_URL || null,
BEACON_UI_ENABLED: parseBoolean(process.env.BENTO_BEACON_UI_ENABLED),
BEACON_NETWORK_ENABLED: parseBoolean(process.env.BENTO_BEACON_NETWORK_ENABLED),

// Authentication
PUBLIC_URL: process.env.BENTO_PUBLIC_URL || null,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bento_public",
"version": "0.21.0",
"version": "0.22.0",
"description": "A publicly accessible portal for clinical datasets, where users are able to see high-level statistics of the data available through predefined variables of interest and search the data using limited variables at a time. This portal allows users to gain a generic understanding of the data available (secure and firewalled) without the need to access it directly. Initially, this portal facilitates the search in English language only, but the French language will be added at a later time.",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/Beacon/BeaconCommon/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const NetworkFilterToggle = () => {
const { isQuerySectionsUnion } = useBeaconNetwork();

return (
<Tooltip title="Choose all search filters across the network, or only those common to all beacons.">
<Tooltip title={t('beacon.network_filter_toggle_help')}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Switch
onChange={() => dispatch(toggleQuerySectionsUnionOrIntersection())}
Expand Down
35 changes: 21 additions & 14 deletions src/js/components/Beacon/BeaconCommon/VariantsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type CSSProperties, useEffect, useMemo } from 'react';
import { type CSSProperties, useEffect, useState } from 'react';

import { Col, Form, Row } from 'antd';
import type { DefaultOptionType } from 'antd/es/select/index';
Expand Down Expand Up @@ -70,17 +70,24 @@ const VariantsForm = ({ isNetworkQuery, beaconAssemblyIds }: VariantsFormProps)
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 [availableContigs, setAvailableContigs] = useState<ContigOptionType[]>([]);

useEffect(() => {
// Right now, we cannot figure out the contig options for the network, so we fall back to a normal input box.
if (!isNetworkQuery && currentAssemblyID && genomesByID[currentAssemblyID]) {
setAvailableContigs(
genomesByID[currentAssemblyID].contigs
.map(contigToOption)
.sort(contigOptionSort)
.filter(filterOutHumanLikeExtraContigs)
);
} else {
// Keep existing memory address for existing empty array if availableContigs was already empty, to avoid
// re-render/clearing effect.
setAvailableContigs((ac) => (ac.length ? [] : ac));
}
}, [isNetworkQuery, currentAssemblyID, genomesByID]);

const assemblySelect = !!availableContigs.length;

useEffect(() => {
Expand All @@ -92,7 +99,7 @@ const VariantsForm = ({ isNetworkQuery, beaconAssemblyIds }: VariantsFormProps)
const formFields = {
referenceName: {
name: 'Chromosome',
placeholder: !currentAssemblyID ? t('beacon.select_asm') : '',
placeholder: !isNetworkQuery && !currentAssemblyID ? t('beacon.select_asm') : '',
initialValue: '',
},
start: {
Expand Down Expand Up @@ -130,7 +137,7 @@ const VariantsForm = ({ isNetworkQuery, beaconAssemblyIds }: VariantsFormProps)
<Col span={8}>
<VariantInput
field={formFields.referenceName}
disabled={variantsError || !currentAssemblyID}
disabled={variantsError || (!isNetworkQuery && !currentAssemblyID)}
mode={assemblySelect ? { type: 'select', options: availableContigs } : { type: 'input' }}
/>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const NetworkSearchResults = () => {
// currently not possible to have both a network error and results, but this may change in the future
const resultsExtra = (
<Space>
{hasBeaconNetworkError && <Tag color="red">Network Error</Tag>}
{hasBeaconNetworkError && <Tag color="red">{t('beacon.network_error')}</Tag>}
{!noResponsesYet && isFetchingAtLeastOneResponse && (
<Spin size="small" indicator={<Loading3QuartersOutlined spin={true} />} />
)}
Expand Down
13 changes: 5 additions & 8 deletions src/js/components/BentoAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useAppDispatch } from '@/hooks';

import { makeGetConfigRequest, makeGetServiceInfoRequest } from '@/features/config/config.store';
import { makeGetAboutRequest } from '@/features/content/content.store';
import { makeGetDataRequestThunk, populateClickable } from '@/features/data/data.store';
import { makeGetDataRequestThunk } from '@/features/data/data.store';
import { makeGetKatsuPublic, makeGetSearchFields } from '@/features/search/query.store';
import { getBeaconConfig } from '@/features/beacon/beacon.store';
import { getBeaconNetworkConfig } from '@/features/beacon/network.store';
Expand All @@ -18,6 +18,7 @@ import { getGenomes } from '@/features/reference/reference.store';
import Loader from '@/components/Loader';
import DefaultLayout from '@/components/Util/DefaultLayout';
import { BEACON_NETWORK_ENABLED } from '@/config';
import { WAITING_STATES } from '@/constants/requests';
import { RequestStatus } from '@/types/requests';
import { BentoRoute } from '@/types/routes';
import { scopeEqual, validProjectDataset } from '@/utils/router';
Expand All @@ -35,7 +36,7 @@ const ScopedRoute = () => {
const { selectedScope, projects, projectsStatus } = useMetadata();

useEffect(() => {
if ([RequestStatus.Idle, RequestStatus.Pending].includes(projectsStatus)) return; // Wait for projects to load first
if (WAITING_STATES.includes(projectsStatus)) return; // Wait for projects to load first

// Update selectedScope based on URL parameters
const valid = validProjectDataset(projects, { project: projectId, dataset: datasetId });
Expand Down Expand Up @@ -92,12 +93,8 @@ const BentoAppRouter = () => {
}

dispatch(makeGetAboutRequest());
// The "Populate clickable" action needs both chart sections and search fields to be available.
// TODO: this is not a very good pattern. It would be better to have a memoized way of determining click-ability at
// render time.
Promise.all([dispatch(makeGetDataRequestThunk()), dispatch(makeGetSearchFields())]).then(() =>
dispatch(populateClickable())
);
dispatch(makeGetDataRequestThunk());
dispatch(makeGetSearchFields());
dispatch(makeGetKatsuPublic());
dispatch(fetchKatsuData());
}, [dispatch, isAuthenticated, selectedScope]);
Expand Down
3 changes: 2 additions & 1 deletion src/js/components/Overview/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) =
};

const { chart_type: type } = chartConfig;
units = t(units); // Units can be a word, like "years". Make sure this word gets translated.

switch (type) {
case CHART_TYPE_BAR:
Expand Down Expand Up @@ -101,7 +102,7 @@ const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) =
}}
renderPopupBody={(_f, d) => (
<>
Count: {(d ?? 0).toString()} {units}
{t('Count') + ':'} {(d ?? 0).toString()} {units}
</>
)}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/js/components/Overview/ChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import SmallChartCardTitle from '@/components/Util/SmallChartCardTitle';
const CARD_STYLE: CSSProperties = { height: '415px', borderRadius: '11px', ...BOX_SHADOW };
const ROW_EMPTY_STYLE: CSSProperties = { height: `${CHART_HEIGHT}px` };

const ChartCard = memo(({ section, chart, onRemoveChart }: ChartCardProps) => {
const ChartCard = memo(({ section, chart, onRemoveChart, searchable }: ChartCardProps) => {
const t = useTranslationFn();
const containerRef = useRef<HTMLDivElement>(null);
const width = useElementWidth(containerRef, chart.width);
Expand All @@ -21,7 +21,6 @@ const ChartCard = memo(({ section, chart, onRemoveChart }: ChartCardProps) => {
data,
field: { id, description, title, config },
chartConfig,
isSearchable,
} = chart;

const extraOptionsData = [
Expand Down Expand Up @@ -59,7 +58,7 @@ const ChartCard = memo(({ section, chart, onRemoveChart }: ChartCardProps) => {
units={config?.units || ''}
id={id}
key={id}
isClickable={isSearchable}
isClickable={!!searchable}
/>
) : (
<Row style={ROW_EMPTY_STYLE} justify="center" align="middle">
Expand All @@ -77,6 +76,7 @@ export interface ChartCardProps {
section: string;
chart: ChartDataField;
onRemoveChart: (arg: { section: string; id: string }) => void;
searchable?: boolean;
}

export default ChartCard;
55 changes: 22 additions & 33 deletions src/js/components/Overview/Counts.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { type CSSProperties, type ReactNode } from 'react';
import { Card, Popover, Space, Statistic, Typography } from 'antd';
import { ExperimentOutlined, InfoCircleOutlined, TeamOutlined } from '@ant-design/icons';
import type { CSSProperties, ReactNode } from 'react';
import { Card, Space, Statistic, Typography } from 'antd';
import { ExperimentOutlined, TeamOutlined } from '@ant-design/icons';
import { BiDna } from 'react-icons/bi';

import { T_PLURAL_COUNT } from '@/constants/i18n';
import CountsTitleWithHelp from '@/components/Util/CountsTitleWithHelp';
import { BOX_SHADOW, COUNTS_FILL } from '@/constants/overviewConstants';
import { WAITING_STATES } from '@/constants/requests';
import { NO_RESULTS_DASHES } from '@/constants/searchConstants';
import { useAppSelector, useTranslationFn } from '@/hooks';
import { useCanSeeUncensoredCounts } from '@/hooks/censorship';
import type { BentoEntity } from '@/types/entities';

const styles: Record<string, CSSProperties> = {
countCard: {
Expand All @@ -17,17 +19,17 @@ const styles: Record<string, CSSProperties> = {
},
};

const CountsHelp = ({ children }: { children: ReactNode }) => <div style={{ maxWidth: 360 }}>{children}</div>;
type CountEntry = { entity: BentoEntity; icon: ReactNode; count: number };

const Counts = () => {
const t = useTranslationFn();

const { counts, isFetchingData } = useAppSelector((state) => state.data);
const { counts, status } = useAppSelector((state) => state.data);

const uncensoredCounts = useCanSeeUncensoredCounts();

// Break down help into multiple sentences inside an array to make translation a bit easier.
const data = [
const data: CountEntry[] = [
{
entity: 'individual',
icon: <TeamOutlined />,
Expand All @@ -45,36 +47,23 @@ const Counts = () => {
},
];

const waitingForData = WAITING_STATES.includes(status);

return (
<>
<Typography.Title level={3}>{t('Counts')}</Typography.Title>
<Space wrap>
{data.map(({ entity, icon, count }, i) => {
const title = t(`entities.${entity}`, T_PLURAL_COUNT);
return (
<Card key={i} style={{ ...styles.countCard, height: isFetchingData ? 138 : 114 }}>
<Statistic
title={
<Space>
{title}
{
<Popover
title={title}
content={<CountsHelp>{t(`entities.${entity}_help`, { joinArrays: ' ' })}</CountsHelp>}
>
<InfoCircleOutlined />
</Popover>
}
</Space>
}
value={count || (uncensoredCounts ? count : NO_RESULTS_DASHES)}
valueStyle={{ color: COUNTS_FILL }}
prefix={icon}
loading={isFetchingData}
/>
</Card>
);
})}
{data.map(({ entity, icon, count }, i) => (
<Card key={i} style={{ ...styles.countCard, height: waitingForData ? 138 : 114 }}>
<Statistic
title={<CountsTitleWithHelp entity={entity} />}
value={count || (uncensoredCounts ? count : NO_RESULTS_DASHES)}
valueStyle={{ color: COUNTS_FILL }}
prefix={icon}
loading={waitingForData}
/>
</Card>
))}
</Space>
</>
);
Expand Down
16 changes: 11 additions & 5 deletions src/js/components/Overview/Drawer/ManageChartsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@ import ChartTree from './ChartTree';

import type { ChartDataField } from '@/types/data';
import { useAppSelector, useAppDispatch, useTranslationFn } from '@/hooks';
import { useSmallScreen } from '@/hooks/useResponsiveContext';
import { hideAllSectionCharts, setAllDisplayedCharts, resetLayout } from '@/features/data/data.store';

const ManageChartsDrawer = ({ onManageDrawerClose, manageDrawerVisible }: ManageChartsDrawerProps) => {
const t = useTranslationFn();

const dispatch = useAppDispatch();

const sections = useAppSelector((state) => state.data.sections);
const isSmallScreen = useSmallScreen();

const { sections } = useAppSelector((state) => state.data);

return (
<Drawer
title={t('Manage Charts')}
placement="right"
onClose={onManageDrawerClose}
open={manageDrawerVisible}
// If we're on a small device, make the drawer full-screen width instead of a fixed width.
// The default value for Ant Design is 372.
width={isSmallScreen ? '100vw' : 420}
extra={
<Space>
<Button
Expand All @@ -29,15 +35,15 @@ const ManageChartsDrawer = ({ onManageDrawerClose, manageDrawerVisible }: Manage
dispatch(setAllDisplayedCharts({}));
}}
>
Show All
{t('Show All')}
</Button>
<Button
size="small"
onClick={() => {
dispatch(resetLayout());
}}
>
Reset
{t('Reset')}
</Button>
</Space>
}
Expand All @@ -55,15 +61,15 @@ const ManageChartsDrawer = ({ onManageDrawerClose, manageDrawerVisible }: Manage
dispatch(setAllDisplayedCharts({ section: sectionTitle }));
}}
>
Show All
{t('Show All')}
</Button>
<Button
size="small"
onClick={() => {
dispatch(hideAllSectionCharts({ section: sectionTitle }));
}}
>
Hide All
{t('Hide All')}
</Button>
</Space>
</Flex>
Expand Down
Loading

0 comments on commit 1869850

Please sign in to comment.