Skip to content

Commit

Permalink
NickAkhmetov/CAT-1078 Fix unselectable collections tabs (#3688)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickAkhmetov authored Feb 12, 2025
1 parent 13fdbc7 commit e96d25d
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 143 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-cat-1078.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix collections tabs being unselectable.
Original file line number Diff line number Diff line change
@@ -1,76 +1,35 @@
import React, { useEffect } from 'react';
import React from 'react';

import PanelList from 'js/shared-styles/panels/PanelList';
import { useFlaskDataContext } from 'js/components/Contexts';
import { CollapsibleDetailPageSection } from 'js/components/detailPage/DetailPageSection';
import { buildCollectionsPanelsProps } from 'js/pages/Collections/utils';
import { useDatasetsCollections } from 'js/hooks/useDatasetsCollections';
import { useDatasetsCollectionsTabs } from 'js/hooks/useDatasetsCollections';
import { Tabs, Tab, TabPanel } from 'js/shared-styles/tables/TableTabs';
import { OutlinedAlert } from 'js/shared-styles/alerts/OutlinedAlert.stories';
import withShouldDisplay from 'js/helpers/withShouldDisplay';
import { sectionIconMap } from 'js/shared-styles/icons/sectionIconMap';
import { useTabs } from 'js/shared-styles/tabs';
import { SectionDescription } from 'js/shared-styles/sections/SectionDescription';
import { useProcessedDatasetTabs } from '../ProcessedData/ProcessedDataset/hooks';
import CollectionsSectionProvider, { useCollectionsSectionContext } from './CollectionsSectionContext';
import { useProcessedDatasets } from 'js/pages/Dataset/hooks';
import Skeleton from '@mui/material/Skeleton';
import { CollectionHit } from 'js/pages/Collections/types';

interface CollectionTabProps {
label: string;
uuid: string;
function CollectionPanel({
index,
value,
collections = [],
}: {
index: number;
icon?: React.ComponentType;
}

function CollectionTab({ label, uuid, index, icon: Icon }: CollectionTabProps) {
const collectionsData = useDatasetsCollections([uuid]);
const {
entity: { uuid: primaryDatasetId },
} = useFlaskDataContext();
const { processedDatasetHasCollections } = useCollectionsSectionContext();

const isPrimaryDataset = uuid === primaryDatasetId;

if (!collectionsData || (collectionsData.length === 0 && uuid !== primaryDatasetId)) {
return null;
}
const isSingleTab = !processedDatasetHasCollections && isPrimaryDataset;

return (
<Tab
label={label}
isSingleTab={isSingleTab}
index={index}
icon={Icon ? <Icon /> : undefined}
iconPosition="start"
/>
);
}

function CollectionPanel({ uuid, index, value }: { uuid: string; index: number; value: number }) {
const collectionsData = useDatasetsCollections([uuid]);
const { setProcessedDatasetHasCollections } = useCollectionsSectionContext();
const {
entity: { uuid: primaryDatasetId },
} = useFlaskDataContext();

useEffect(() => {
if (uuid !== primaryDatasetId && collectionsData?.length > 0) {
setProcessedDatasetHasCollections(true);
}
}, [collectionsData?.length, primaryDatasetId, setProcessedDatasetHasCollections, uuid]);
if (!collectionsData) {
return null;
}
const panelsProps = buildCollectionsPanelsProps(collectionsData);
value: number;
collections: CollectionHit[];
}) {
const panelsProps = buildCollectionsPanelsProps(collections);
if (panelsProps.length === 0) {
if (uuid === primaryDatasetId) {
return (
<TabPanel value={value} index={index}>
<OutlinedAlert severity="info">The raw dataset is not referenced in any existing collections.</OutlinedAlert>
</TabPanel>
);
}
return null;
return (
<TabPanel value={value} index={index} sx={{ '> .MuiPaper-root': { width: '100%' } }}>
<OutlinedAlert severity="info">The raw dataset is not referenced in any existing collections.</OutlinedAlert>
</TabPanel>
);
}
return (
<TabPanel value={value} index={index}>
Expand All @@ -83,23 +42,51 @@ const collectionsSectionDescription =
'Collections may contain references to either raw or processed datasets. If a processed dataset is not included in any collection, there will be no corresponding tabs in the table below.';

function CollectionsSection() {
const processedDatasetTabs = useProcessedDatasetTabs();
const datasetCollectionsTabs = useDatasetsCollectionsTabs();

const { openTabIndex, handleTabChange } = useTabs();
const { isLoading } = useProcessedDatasets();

const { openTabIndex, handleTabChange, setOpenTabIndex } = useTabs(0);

if (isLoading) {
return (
<CollapsibleDetailPageSection id="collections" title="Collections" icon={sectionIconMap.collections}>
<SectionDescription>{collectionsSectionDescription}</SectionDescription>
<Skeleton variant="rectangular" height={200} />
</CollapsibleDetailPageSection>
);
}

if (datasetCollectionsTabs.length === 0) {
return null;
}

return (
<CollapsibleDetailPageSection id="collections" title="Collections" icon={sectionIconMap.collections}>
<CollectionsSectionProvider>
<SectionDescription>{collectionsSectionDescription}</SectionDescription>
<Tabs value={openTabIndex} onChange={handleTabChange} aria-label="Dataset collections">
{processedDatasetTabs.map(({ label, uuid, icon }, index) => (
<CollectionTab key={uuid} label={label} uuid={uuid} index={index} icon={icon} />
))}
</Tabs>
{processedDatasetTabs.map(({ uuid }, index) => (
<CollectionPanel key={uuid} uuid={uuid} index={index} value={openTabIndex} />
<SectionDescription>{collectionsSectionDescription}</SectionDescription>
<Tabs
value={openTabIndex}
onChange={(e, newValue) => {
handleTabChange(e, newValue as number);
}}
aria-label="Dataset collections"
>
{datasetCollectionsTabs.map(({ label, uuid, icon: Icon }, index) => (
<Tab
key={uuid}
label={label}
isSingleTab={datasetCollectionsTabs.length === 1}
index={index}
icon={Icon ? <Icon /> : undefined}
iconPosition="start"
onClick={() => setOpenTabIndex(index)}
value={index}
/>
))}
</CollectionsSectionProvider>
</Tabs>
{datasetCollectionsTabs.map(({ uuid, collections }, index) => (
<CollectionPanel key={uuid} index={index} value={openTabIndex} collections={collections} />
))}
</CollapsibleDetailPageSection>
);
}
Expand Down

This file was deleted.

10 changes: 10 additions & 0 deletions context/app/static/js/helpers/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ export const includeOnlyDatasetsClause: QueryDslQueryContainer = {
},
};

export const includeOnlyCollectionsClause: QueryDslQueryContainer = {
bool: {
must: {
term: {
'entity_type.keyword': 'Collection',
},
},
},
};

export function getAncestorsQuery(descendantUUID: string): QueryDslQueryContainer {
return {
bool: {
Expand Down
55 changes: 53 additions & 2 deletions context/app/static/js/hooks/useDatasetsCollections.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useSearchHits } from 'js/hooks/useSearchData';
import { getAllCollectionsQuery } from 'js/helpers/queries';
import { Collection } from 'js/pages/Collections/types';
import { Collection, CollectionHit } from 'js/pages/Collections/types';
import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import { useMemo } from 'react';
import { useFlaskDataContext } from 'js/components/Contexts';
import { useProcessedDatasetTabs } from 'js/components/detailPage/ProcessedData/ProcessedDataset/hooks';

function buildCollectionsWithDatasetQuery(datasetUUIDs: string[]): SearchRequest {
return {
Expand All @@ -11,6 +14,7 @@ function buildCollectionsWithDatasetQuery(datasetUUIDs: string[]): SearchRequest
'datasets.uuid': datasetUUIDs,
},
},
_source: ['uuid', 'title', 'hubmap_id', 'datasets.uuid'],
};
}

Expand All @@ -21,4 +25,51 @@ function useDatasetsCollections(datasetUUIDs: string[]) {
return collections;
}

export { useDatasetsCollections, buildCollectionsWithDatasetQuery };
function useDatasetsCollectionsTabs() {
const processedDatasetTabs = useProcessedDatasetTabs();
const datasetUUIDs = useMemo(() => processedDatasetTabs.map((tab) => tab.uuid), [processedDatasetTabs]);
const collections = useDatasetsCollections(datasetUUIDs);
const {
entity: { uuid: primaryDatasetId },
} = useFlaskDataContext();

const collectionsMap = useMemo(
() =>
datasetUUIDs.reduce(
(acc, datasetUUID) => {
const collectionsContainingDataset = collections.filter((collection) => {
const { datasets } = collection._source;
if (!datasets) {
return false;
}
return datasets.some((dataset) => dataset.uuid === datasetUUID);
});
if (collectionsContainingDataset.length > 0 || datasetUUID === primaryDatasetId) {
acc[datasetUUID] = collectionsContainingDataset;
}
return acc;
},
{ [primaryDatasetId]: [] } as Record<string, CollectionHit[]>,
),
[collections, datasetUUIDs, primaryDatasetId],
);

return useMemo(() => {
let atLeastOneCollection = false;
return processedDatasetTabs
.map((processedDatasetTab) => {
const { uuid } = processedDatasetTab;
const collectionsForDataset = collectionsMap[uuid];
if (collectionsForDataset?.length > 0) {
atLeastOneCollection = true;
}
return {
...processedDatasetTab,
collections: collectionsForDataset,
};
})
.filter((tab) => tab.collections?.length > 0 || (atLeastOneCollection && tab.uuid === primaryDatasetId));
}, [collectionsMap, processedDatasetTabs, primaryDatasetId]);
}

export { useDatasetsCollections, useDatasetsCollectionsTabs, buildCollectionsWithDatasetQuery };
2 changes: 1 addition & 1 deletion context/app/static/js/pages/Collections/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface Collection {
uuid: string;
title: string;
hubmap_id: string;
datasets: { hubmap_id: string }[];
datasets: { hubmap_id: string; uuid: string }[];
}

type CollectionHit = Pick<Required<SearchHit<Collection>>, '_source'>;
Expand Down
6 changes: 5 additions & 1 deletion context/app/static/js/pages/Collections/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ test('should return the props require for the panel list', () => {
uuid: 'abc123',
title: 'Collection ABC',
hubmap_id: 'HBM_ABC123',
datasets: [{ hubmap_id: 'a' }, { hubmap_id: 'b' }, { hubmap_id: 'c' }],
datasets: [
{ hubmap_id: 'a', uuid: 'a123' },
{ hubmap_id: 'b', uuid: 'b234' },
{ hubmap_id: 'c', uuid: 'c345' },
],
},
},
];
Expand Down
2 changes: 1 addition & 1 deletion context/app/static/js/shared-styles/alerts/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ const StyledAlert = styled(OutlinedAlert)<StyledAlertProps>(({ theme, $marginBot
marginBottom: $marginBottom ?? 0,
marginTop: $marginTop ?? 0,
width: $width ?? 'auto',
}));
})) as React.ComponentType<StyledAlertProps>;

export { StyledAlert as Alert };
2 changes: 1 addition & 1 deletion context/app/static/js/shared-styles/tabs/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function useTabs(initialTabIndex = 0) {
setOpenTabIndex(newValue);
});

return { openTabIndex, handleTabChange };
return { openTabIndex, handleTabChange, setOpenTabIndex };
}

export { useTabs };
Loading

0 comments on commit e96d25d

Please sign in to comment.