Skip to content

Commit

Permalink
Austenem/CAT-1019 Provenance table banner (#3610)
Browse files Browse the repository at this point in the history
* add alert to table

* add changelog

* fix detail page logic

* add usecallback
  • Loading branch information
austenem authored Nov 19, 2024
1 parent 454e147 commit 5b45db8
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-provenance-table-banner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add warning banner to empty columns in provenance tables.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import Skeleton from '@mui/material/Skeleton';
Expand All @@ -9,6 +9,7 @@ import { entityIconMap } from 'js/shared-styles/icons/entityIconMap';
import { useTrackEntityPageEvent } from 'js/components/detailPage/useTrackEntityPageEvent';
import { useEntitiesData } from 'js/hooks/useEntityData';
import { ErrorTile } from 'js/components/entity-tile/EntityTile/EntityTile';
import { Alert } from 'js/shared-styles/alerts';
import { FlexContainer, FlexColumn, TableColumn, StyledSvgIcon, ProvTableEntityHeader } from './style';
import ProvTableTile from '../ProvTableTile';
import ProvTableDerivedLink from '../ProvTableDerivedLink';
Expand All @@ -26,70 +27,95 @@ interface ProvTableColumnProps {
missingAncestors?: string[];
}

function ProvEntityColumn({
function ProvEntityColumnContent({
type,
entities,
currentEntityUUID,
descendantEntityCounts,
missingAncestors,
}: ProvTableColumnProps) {
const trackEntityPageEvent = useTrackEntityPageEvent();
const handleCardSelect = useCallback(
(hubmap_id: string) => {
trackEntityPageEvent({
action: 'Provenance / Table / Select Card',
label: hubmap_id,
});
},
[trackEntityPageEvent],
);

// Track expanded state for each sample category
const [isExpanded, setIsExpanded] = useState<Record<string, boolean>>({});

const displayMissingAncestors =
missingAncestors && missingAncestors.length > 0 && entities.length === 0 && type !== 'Dataset';
const noDisplayedContent = entities.length === 0 && !displayMissingAncestors && !descendantEntityCounts?.[type];

if (noDisplayedContent) {
return (
<Alert severity="warning" $width="100%">
No {type.toLowerCase()}s available.
</Alert>
);
}

return (
<>
{entities.length > 0 &&
entities
.sort((a, b) => a.created_timestamp - b.created_timestamp)
.map((item, j, items) => {
const isSampleSibling =
j > 0 && item.entity_type === 'Sample' && items[j - 1]?.sample_category === item.sample_category;
if (isSampleSibling && !isExpanded[item.sample_category as string]) {
const siblings = items.filter((i) => i.sample_category === item.sample_category);
const numberOfSiblings = siblings.length - 1;
// Only draw the button once for the first sibling in the list
// First element in the siblings list is the first item in the category,
// so the second item in the list is the first sibling.
const isFirstSibling = siblings.findIndex((i) => i.uuid === item.uuid) === 1;
if (!isFirstSibling) return null;
return (
<Button
key={item.uuid}
variant="contained"
onClick={() => setIsExpanded((s) => ({ ...s, [item.sample_category as string]: true }))}
>
View More in Category ({numberOfSiblings})
</Button>
);
}
return (
<ProvTableTile
key={item.uuid}
uuid={item.uuid}
id={item.hubmap_id}
entity_type={item.entity_type}
isCurrentEntity={currentEntityUUID === item.uuid}
isSampleSibling={isSampleSibling}
isFirstTile={j === 0}
isLastTile={j === type.length - 1}
onClick={() => handleCardSelect(item.hubmap_id)}
entityData={item}
/>
);
})}
{descendantEntityCounts?.[type] && <ProvTableDerivedLink uuid={currentEntityUUID} type={type} />}
{displayMissingAncestors && missingAncestors.map((id) => <ErrorTile key={id} entity_type={type} id={id} />)}
</>
);
}

function ProvEntityColumn({ type, ...rest }: ProvTableColumnProps) {
return (
<TableColumn key={`provenance-list-${type.toLowerCase()}`}>
<ProvTableEntityHeader>
<StyledSvgIcon as={entityIconMap[type as ESEntityType]} color="primary" />
<Typography variant="h5">{type}s</Typography>
</ProvTableEntityHeader>
<FlexColumn>
{entities.length > 0 &&
entities
.sort((a, b) => a.created_timestamp - b.created_timestamp)
.map((item, j, items) => {
const isSampleSibling =
j > 0 && item.entity_type === 'Sample' && items[j - 1]?.sample_category === item.sample_category;
if (isSampleSibling && !isExpanded[item.sample_category as string]) {
const siblings = items.filter((i) => i.sample_category === item.sample_category);
const numberOfSiblings = siblings.length - 1;
// Only draw the button once for the first sibling in the list
// First element in the siblings list is the first item in the category,
// so the second item in the list is the first sibling.
const isFirstSibling = siblings.findIndex((i) => i.uuid === item.uuid) === 1;
if (!isFirstSibling) return null;
return (
<Button
key={item.uuid}
variant="contained"
onClick={() => setIsExpanded((s) => ({ ...s, [item.sample_category as string]: true }))}
>
View More in Category ({numberOfSiblings})
</Button>
);
}
return (
<ProvTableTile
key={item.uuid}
uuid={item.uuid}
id={item.hubmap_id}
entity_type={item.entity_type}
isCurrentEntity={currentEntityUUID === item.uuid}
isSampleSibling={isSampleSibling}
isFirstTile={j === 0}
isLastTile={j === type.length - 1}
onClick={() =>
trackEntityPageEvent({ action: 'Provenance / Table / Select Card', label: item.hubmap_id })
}
entityData={item}
/>
);
})}
{descendantEntityCounts?.[type] && <ProvTableDerivedLink uuid={currentEntityUUID} type={type} />}
{displayMissingAncestors && missingAncestors.map((id) => <ErrorTile key={id} entity_type={type} id={id} />)}
<ProvEntityColumnContent type={type} {...rest} />
</FlexColumn>
</TableColumn>
);
Expand Down
4 changes: 3 additions & 1 deletion context/app/static/js/shared-styles/alerts/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ function OutlinedAlert(props: AlertProps) {
interface StyledAlertProps extends AlertProps {
$marginBottom?: number;
$marginTop?: number;
$width?: string;
}

// TODO: Figure out why `sx` doesn't work with this component. https://hms-dbmi.atlassian.net/browse/CAT-650
const StyledAlert = styled(OutlinedAlert)<StyledAlertProps>(({ theme, $marginBottom, $marginTop }) => ({
const StyledAlert = styled(OutlinedAlert)<StyledAlertProps>(({ theme, $marginBottom, $marginTop, $width }) => ({
'> &:not(svg)': {
color: theme.palette.text.primary,
},
Expand All @@ -36,6 +37,7 @@ const StyledAlert = styled(OutlinedAlert)<StyledAlertProps>(({ theme, $marginBot
},
marginBottom: $marginBottom ?? 0,
marginTop: $marginTop ?? 0,
width: $width ?? 'auto',
}));

export { StyledAlert as Alert };

0 comments on commit 5b45db8

Please sign in to comment.