Skip to content

feat: new search UI #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 63 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9e136c7
feat: new search UI
davidlougheed Mar 18, 2025
eaec5b5
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Mar 18, 2025
e43e829
Merge branch 'refact/request-status' into feat/new-search-ui
davidlougheed Mar 18, 2025
8a9ef33
chore(search): memoize OrDelimiter
davidlougheed Mar 18, 2025
ca8a008
style(search): tweak filter/search card padding
davidlougheed Mar 18, 2025
2122ba3
fix: merge error
davidlougheed Mar 18, 2025
c480148
loading skeleton for filters
davidlougheed Mar 18, 2025
44b374d
Merge remote-tracking branch 'origin/refact/request-status' into feat…
davidlougheed Mar 19, 2025
46b72ce
style(search): form focus opacity
davidlougheed Mar 19, 2025
cc4cef9
refact(search): factor out components
davidlougheed Mar 19, 2025
40b2c58
Merge branch 'refact/request-status' into feat/new-search-ui
davidlougheed Mar 19, 2025
758d001
refact(search): move RequestStatusIcon into component file
davidlougheed Mar 19, 2025
fe399d7
refact(search): split SearchFilters into component file
davidlougheed Mar 19, 2025
7f50bbe
fix(search): fix order flipping on filters change
davidlougheed Mar 19, 2025
0b49046
work on free text search
davidlougheed Mar 20, 2025
008c6d7
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Mar 21, 2025
2c1bd06
style(beacon): flip filters + results sections
davidlougheed Mar 21, 2025
bc1a295
feat(search): showing individuals x of y + i18n
davidlougheed Mar 21, 2025
c80e1da
i18n: project/dataset
davidlougheed Mar 21, 2025
07499bc
style: consistent small search card gutters
davidlougheed Mar 21, 2025
453e531
feat: functional BackTop button for all pages
davidlougheed Mar 21, 2025
57e8bb8
lint: constant for shared vert line style in OrDelimiter
davidlougheed Mar 21, 2025
563ffca
refact: query filter store
davidlougheed Mar 26, 2025
44015b3
feat: full text search URL param
davidlougheed Mar 26, 2025
be12c62
refact(search): reset filter query status when execing FTS
davidlougheed Mar 26, 2025
8f43fee
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Mar 26, 2025
3e0d5a2
sync free text search form with URL param if needed
davidlougheed Mar 26, 2025
9f619a9
fix: build
davidlougheed Mar 26, 2025
a3fca20
refact(search): control SearchResultsPane subpane with props
davidlougheed Mar 26, 2025
a409f36
refact: page/URL utils + keep search pane in URL
davidlougheed Mar 27, 2025
9578d1b
refact(search): memoized info-circle option description
davidlougheed Mar 27, 2025
6c8fdd7
feat(search): switch/reset search form focus
davidlougheed Mar 27, 2025
db462d1
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Mar 27, 2025
927ab5f
refact(config): rm dead state maxQueryParametersRequired
davidlougheed Mar 27, 2025
0c6b7cf
fix: misc load errors
davidlougheed Mar 27, 2025
ce8aefc
refact(search): factor out OrDelimiter
davidlougheed Mar 28, 2025
2cebdca
refact(search): memoization/types work
davidlougheed Mar 28, 2025
5ea1e09
refact(search): factor out SearchResultsTablePage
davidlougheed Mar 28, 2025
f47e1fa
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Mar 28, 2025
0fa986f
prevent blank text query from executing
davidlougheed Mar 28, 2025
98d9ac1
style: more obvious switching between search forms
davidlougheed Apr 14, 2025
53407e3
fix(search): issues with text search not re-executing
davidlougheed Apr 14, 2025
dc63c09
fix(search): don't accidentally clear text search qp
davidlougheed Apr 14, 2025
c399944
fix(search): undefined free text search
davidlougheed Apr 23, 2025
24f01d4
fix(search): reset free text search to redux on focus change
davidlougheed Apr 23, 2025
2ecf2f9
style(search): no idle state icon + section title hover colour
davidlougheed Apr 23, 2025
a62e68d
fix(search): allow blank free text searches
davidlougheed Apr 23, 2025
182bfb9
style: use CSS class for 100% width
davidlougheed Apr 23, 2025
7bd1093
style: more utility classes
davidlougheed Apr 23, 2025
21e22c7
self-closing divs
davidlougheed Apr 23, 2025
038e1d9
lint(beacon)
davidlougheed Apr 23, 2025
7a248b6
style: back to top float button issues + responsive styling
davidlougheed Apr 23, 2025
7efaa65
style(search): small screen styling
davidlougheed Apr 23, 2025
8fecec8
style: consistent XL-sized border radii via CSS class
davidlougheed Apr 24, 2025
7bb403e
refact(search): or divider mini component
davidlougheed Apr 24, 2025
f5c30de
style(search): small screen optimizations for search table
davidlougheed Apr 24, 2025
b17cc34
style(search): reduce gap between search forms with small screen
davidlougheed Apr 24, 2025
a03ce43
fix some issues with how search handles subpages
davidlougheed Apr 24, 2025
5e430e5
feat(search): populated project/dataset info + column config
davidlougheed Apr 25, 2025
ca01dee
feat(search): port results CSV download from web
davidlougheed Apr 29, 2025
544acc4
Merge remote-tracking branch 'origin/main' into feat/new-search-ui
davidlougheed Apr 30, 2025
0e75697
fix(search): bad start/end table numbering
davidlougheed May 1, 2025
f5b6fd9
search results pane -> page
davidlougheed May 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"bento-auth-js": "^7.2.0",
"bento-charts": "~2.13.1",
"dotenv": "^16.3.1",
"file-saver": "^2.0.5",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.0.4",
"i18next-http-backend": "^3.0.2",
Expand All @@ -42,6 +43,7 @@
"redux-thunk": "^2.4.1"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/json-schema": "^7.0.15",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^7.16.1",
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/Beacon/BeaconCommon/AssemblyIdSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const AssemblyIdSelect = ({ field, beaconAssemblyIds, disabled }: AssemblyIdSele

return (
<Form.Item name={field.name} label={t(field.name)} rules={field.rules}>
<Select style={{ width: '100%' }} disabled={disabled} options={assemblyIdOptions} />
<Select className="w-full" disabled={disabled} options={assemblyIdOptions} />
</Form.Item>
);
};
Expand Down
15 changes: 4 additions & 11 deletions src/js/components/Beacon/BeaconCommon/BeaconQueryFormUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ import type {
BeaconPayloadFilter,
PayloadVariantsQuery,
} from '@/types/beacon';
import { BOX_SHADOW } from '@/constants/overviewConstants';
import {
FORM_ROW_GUTTERS,
CARD_STYLE,
BUTTON_AREA_STYLE,
BUTTON_STYLE,
CARD_STYLES,
} from '@/constants/beaconConstants';
import { FORM_ROW_GUTTERS, BUTTON_AREA_STYLE, BUTTON_STYLE, CARD_STYLES } from '@/constants/beaconConstants';
import { T_PLURAL_COUNT } from '@/constants/i18n';

const STARTER_FILTER = { index: 0, searchFieldId: null };
Expand Down Expand Up @@ -231,15 +224,15 @@ const BeaconQueryFormUi = ({

return (
<div className="container margin-auto" style={{ paddingBottom: 8 }}>
<Card title={t('Search')} style={{ borderRadius: '10px', width: '100%', ...BOX_SHADOW }} styles={CARD_STYLES}>
<Card title={t('Search')} className="w-full shadow rounded-xl" styles={CARD_STYLES}>
<p style={{ margin: '-8px 0 8px 0', padding: '0', color: 'grey' }}>{t(uiInstructions)}</p>
<Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}>
<Row gutter={FORM_ROW_GUTTERS}>
{hasVariants && (
<Col xs={24} lg={12}>
<Card
title={t('entities.variant', T_PLURAL_COUNT)}
style={CARD_STYLE}
className="shadow h-full"
styles={CARD_STYLES}
extra={
<SearchToolTip>
Expand All @@ -258,7 +251,7 @@ const BeaconQueryFormUi = ({
<>{t('Metadata')}</>
</div>
}
style={CARD_STYLE}
className="shadow h-full"
styles={CARD_STYLES}
extra={
<SearchToolTip>
Expand Down
8 changes: 2 additions & 6 deletions src/js/components/Beacon/BeaconCommon/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type {
} from '@/types/beacon';

const FILTER_FORM_ITEM_STYLE = { flex: 1, marginInlineEnd: -1 };
const FILTER_FORM_ITEM_INNER_STYLE = { width: '100%' };

const Filter = ({
filter,
Expand Down Expand Up @@ -74,7 +73,7 @@ const Filter = ({
>
<Select
placeholder={t('select a search field')}
style={FILTER_FORM_ITEM_INNER_STYLE}
className="w-full"
onSelect={handleSelectKey}
options={searchKeyOptions(beaconFiltersBySection)}
/>
Expand All @@ -84,10 +83,7 @@ const Filter = ({
rules={[{ required: isRequired, message: t('value required') }]}
style={FILTER_FORM_ITEM_STYLE}
>
<Select
style={FILTER_FORM_ITEM_INNER_STYLE}
options={valueOptions.map(({ label, value }) => ({ label: t(label), value }))}
/>
<Select className="w-full" options={valueOptions.map(({ label, value }) => ({ label: t(label), value }))} />
</Form.Item>
<Button onClick={() => removeFilter(filter)}>
<CloseOutlined />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Card, Tag, Tooltip, Typography } from 'antd';
import { LinkOutlined } from '@ant-design/icons';
import type { NetworkBeacon } from '@/types/beaconNetwork';
import type { FlattenedBeaconResponse } from '@/types/beacon';
import { BOX_SHADOW } from '@/constants/overviewConstants';
import NodeCountsDisplay from './NodeCountsDisplay';
import { useTranslationFn } from '@/hooks';

Expand Down Expand Up @@ -56,7 +55,7 @@ const NodeDetails = ({ beacon, response }: NodeDetailsProps) => {
return (
<Card
title={logoAndName}
style={BOX_SHADOW}
className="shadow"
styles={{
body: { flexGrow: 1, minHeight: CARD_BODY_MIN_HEIGHT },
actions: { display: 'flex', alignItems: 'center' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const NetworkSearchResults = () => {
results={networkResults}
resultsTitle={t('beacon.network_search_results')}
resultsExtra={resultsExtra}
style={{ marginBottom: 8 }}
/>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/Beacon/BeaconQueryUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const BeaconQueryUi = () => {
<Loader />
) : (
<div style={WRAPPER_STYLE}>
<BeaconSearchResults />
<BeaconQueryFormUi
isFetchingQueryResponse={WAITING_STATES.includes(queryStatus)}
isNetworkQuery={false}
Expand All @@ -23,6 +22,7 @@ const BeaconQueryUi = () => {
apiErrorMessage={apiErrorMessage}
beaconFiltersBySection={beaconFilters}
/>
<BeaconSearchResults />
</div>
);
};
Expand Down
39 changes: 16 additions & 23 deletions src/js/components/BentoAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ import { BEACON_UI_ENABLED, 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';
import {
getPathPageIndex,
langAndScopeSelectionToUrl,
pathParts,
scopeEqual,
validProjectDataset,
} from '@/utils/router';

import PublicOverview from './Overview/PublicOverview';
import Search from './Search/Search';
Expand Down Expand Up @@ -54,7 +60,7 @@ const ScopedRoute = () => {
// If the URL scope is valid, store the scope in the Redux store.
// We have two subcases here:
// - If the validated scope matches the URL parameters, nothing needs to be done
// - No parameters have been supplied and we have a single-dataset node, in which case we want to keep the "clean"
// - No parameters have been supplied, and we have a single-dataset node, in which case we want to keep the "clean"
// / blank URL to avoid visual clutter.
if (
(datasetId === valid.scope.dataset && projectId === valid.scope.project) ||
Expand All @@ -66,25 +72,12 @@ const ScopedRoute = () => {

// Otherwise: validated scope does not match our desired URL params, so we need to re-locate to a valid path.

const oldPath = location.pathname.split('/').filter(Boolean);
const newPath = [oldPath[0]];

// If we have >1 dataset, we need the URL to match the validated scope, so we create a new path and go there.
// Otherwise (with 1 dataset), keep URL as clean as possible - with no IDs present at all.
if (!isFixedProjectAndDataset) {
if (valid.scope.dataset) {
newPath.push('p', valid.scope.project as string, 'd', valid.scope.dataset);
} else if (valid.scope.project) {
newPath.push('p', valid.scope.project);
}
}
const oldPath = pathParts(location.pathname);
const oldPathPageIdx = getPathPageIndex(oldPath);
const newPathSuffix = oldPath.slice(oldPathPageIdx).join('/');
const newPath = langAndScopeSelectionToUrl(oldPath[0], valid, newPathSuffix);

const oldPathLength = oldPath.length;
if (oldPath[oldPathLength - 3] === 'p' || oldPath[oldPathLength - 3] === 'd') {
newPath.push(oldPath[oldPathLength - 1]);
}
const newPathString = '/' + newPath.join('/');
navigate(newPathString, { replace: true });
navigate(newPath, { replace: true });
}, [projectsByID, projectsStatus, projectId, datasetId, dispatch, navigate, selectedScope]);

return <Outlet />;
Expand Down Expand Up @@ -155,7 +148,7 @@ const BentoAppRouter = () => {
<Route path="/" element={<ScopedRoute />}>
<Route index element={<PublicOverview />} />
<Route path={BentoRoute.Overview} element={<PublicOverview />} />
<Route path={BentoRoute.Search} element={<Search />} />
<Route path={`${BentoRoute.Search}/:page?`} element={<Search />} />
{BentoRoute.Beacon && <Route path={BentoRoute.Beacon} element={<BeaconQueryUi />} />}
{/* Beacon network is only available at the top level - scoping does not make sense for it. */}
{BentoRoute.BeaconNetwork && <Route path={BentoRoute.BeaconNetwork} element={<NetworkUi />} />}
Expand All @@ -165,15 +158,15 @@ const BentoAppRouter = () => {
<Route path="/p/:projectId" element={<ScopedRoute />}>
<Route index element={<PublicOverview />} />
<Route path={BentoRoute.Overview} element={<PublicOverview />} />
<Route path={BentoRoute.Search} element={<Search />} />
<Route path={`${BentoRoute.Search}/:page?`} element={<Search />} />
{BentoRoute.Beacon && <Route path={BentoRoute.Beacon} element={<BeaconQueryUi />} />}
<Route path={BentoRoute.Provenance} element={<ProvenanceTab />} />
</Route>

<Route path="/p/:projectId/d/:datasetId" element={<ScopedRoute />}>
<Route index element={<PublicOverview />} />
<Route path={BentoRoute.Overview} element={<PublicOverview />} />
<Route path={BentoRoute.Search} element={<Search />} />
<Route path={`${BentoRoute.Search}/:page?`} element={<Search />} />
{BentoRoute.Beacon && <Route path={BentoRoute.Beacon} element={<BeaconQueryUi />} />}
<Route path={BentoRoute.Provenance} element={<ProvenanceTab />} />
</Route>
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/ClinPhen/TimeElementDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { memo } from 'react';

import { EM_DASH } from '@/constants/contentConstants';
import { EM_DASH } from '@/constants/common';
import type {
TimeElement,
TimeElementAge,
Expand Down
5 changes: 1 addition & 4 deletions src/js/components/Overview/AboutBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import { type CSSProperties, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Divider, Skeleton } from 'antd';

import { BOX_SHADOW } from '@/constants/overviewConstants';
import { useAppSelector } from '@/hooks';
import { useSmallScreen } from '@/hooks/useResponsiveContext';
import { RequestStatus } from '@/types/requests';

const ABOUT_CARD_STYLE: CSSProperties = { borderRadius: '11px', ...BOX_SHADOW };

const AboutBox = ({ style, bottomDivider }: { style?: CSSProperties; bottomDivider?: boolean }) => {
const { i18n } = useTranslation();

Expand All @@ -20,7 +17,7 @@ const AboutBox = ({ style, bottomDivider }: { style?: CSSProperties; bottomDivid
// If about is blank after loading, we don't have anything - so don't render the box.
return aboutStatus === RequestStatus.Fulfilled && !aboutContent ? null : (
<>
<Card className="container" style={{ ...ABOUT_CARD_STYLE, ...(style ?? {}) }}>
<Card className="container shadow rounded-xl" style={style}>
{aboutStatus === RequestStatus.Idle || aboutStatus === RequestStatus.Pending ? (
<Skeleton title={false} paragraph={{ rows: 2 }} />
) : (
Expand Down
5 changes: 3 additions & 2 deletions src/js/components/Overview/ChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { Button, Card, Row, Space, Tooltip } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import Chart from './Chart';
import CustomEmpty from '../Util/CustomEmpty';
import { BOX_SHADOW, CHART_HEIGHT } from '@/constants/overviewConstants';
import { CHART_HEIGHT } from '@/constants/overviewConstants';
import { useElementWidth, useTranslationFn } from '@/hooks';
import type { ChartDataField } from '@/types/data';
import SmallChartCardTitle from '@/components/Util/SmallChartCardTitle';

const CARD_STYLE: CSSProperties = { height: '415px', borderRadius: '11px', ...BOX_SHADOW };
const CARD_STYLE: CSSProperties = { height: '415px' };
const ROW_EMPTY_STYLE: CSSProperties = { height: `${CHART_HEIGHT}px` };

const ChartCard = memo(({ section, chart, onRemoveChart, searchable }: ChartCardProps) => {
Expand Down Expand Up @@ -39,6 +39,7 @@ const ChartCard = memo(({ section, chart, onRemoveChart, searchable }: ChartCard
title={
<SmallChartCardTitle title={t(title)} description={t(description)} descriptionStyle={{ width: '375px' }} />
}
className="shadow rounded-xl"
style={CARD_STYLE}
size="small"
extra={
Expand Down
7 changes: 3 additions & 4 deletions src/js/components/Overview/Counts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { ExperimentOutlined, TeamOutlined } from '@ant-design/icons';
import { BiDna } from 'react-icons/bi';

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

const styles: Record<string, CSSProperties> = {
countCard: {
...BOX_SHADOW,
minWidth: 150,
transition: 'height 0.3s ease-in-out',
},
Expand Down Expand Up @@ -54,7 +53,7 @@ const Counts = () => {
<Typography.Title level={3}>{t('Counts')}</Typography.Title>
<Space wrap>
{data.map(({ entity, icon, count }, i) => (
<Card key={i} style={{ ...styles.countCard, height: waitingForData ? 138 : 114 }}>
<Card key={i} className="shadow" style={{ ...styles.countCard, height: waitingForData ? 138 : 114 }}>
<Statistic
title={<CountsTitleWithHelp entity={entity} />}
value={count || (uncensoredCounts ? count : NO_RESULTS_DASHES)}
Expand Down
5 changes: 2 additions & 3 deletions src/js/components/Overview/LastIngestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';

import { T_PLURAL_COUNT } from '@/constants/i18n';
import { WAITING_STATES } from '@/constants/requests';
import { BOX_SHADOW } from '@/constants/overviewConstants';
import { useDataTypes } from '@/features/dataTypes/hooks';
import type { BentoServiceDataType } from '@/types/dataTypes';
import { useTranslationFn } from '@/hooks';
Expand All @@ -25,7 +24,7 @@ const formatDate = (dateString: string, language: string) => {
};

const LastIngestionSkeleton = () => (
<Card style={BOX_SHADOW}>
<Card className="shadow">
<Space direction="vertical">
<Typography.Text style={{ color: 'rgba(0,0,0,0.45)' }}>
<Skeleton active={true} title={{ width: 100, style: { margin: 0 } }} paragraph={false} />
Expand All @@ -46,7 +45,7 @@ const LastIngestionSkeleton = () => (
const LastIngestionDataType = ({ dataType }: { dataType: BentoServiceDataType }) => {
const { t, i18n } = useTranslation();
return (
<Card style={BOX_SHADOW} key={dataType.id}>
<Card className="shadow" key={dataType.id}>
<Space direction="vertical">
<Typography.Text style={{ color: 'rgba(0,0,0,0.45)' }}>
{t(`entities.${dataType.id}`, T_PLURAL_COUNT)}
Expand Down
Loading