diff --git a/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetails.tsx b/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetails.tsx index 7019286757..deca01089f 100644 --- a/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetails.tsx +++ b/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetails.tsx @@ -13,8 +13,20 @@ function AnalysisDetails({ dagListData }: AnalysisDetails) { return (
- {ingestPipelines.length > 0 && } - {cwlPipelines.length > 0 && } + {ingestPipelines.length > 0 && ( + + )} + {cwlPipelines.length > 0 && ( + + )}
); } diff --git a/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetailsList.tsx b/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetailsList.tsx index ee479cadcb..cdd1918a45 100644 --- a/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetailsList.tsx +++ b/context/app/static/js/components/detailPage/AnalysisDetails/AnalysisDetailsList.tsx @@ -2,6 +2,7 @@ import React from 'react'; import List from '@mui/material/List'; import Typography from '@mui/material/Typography'; +import InfoTooltipIcon from 'js/shared-styles/icons/TooltipIcon'; import ProvAnalysisDetailsLink from './AnalysisDetailsLink'; interface ProvAnalysisDetailsListProps { @@ -11,12 +12,15 @@ interface ProvAnalysisDetailsListProps { origin: string; }[]; pipelineType: string; + tooltip?: string; } -function ProvAnalysisDetailsList({ pipelines, pipelineType }: ProvAnalysisDetailsListProps) { +function ProvAnalysisDetailsList({ pipelines, pipelineType, tooltip }: ProvAnalysisDetailsListProps) { return ( <> - {`${pipelineType} Pipelines`} + + {`${pipelineType} Pipelines`} + {pipelines.map((item) => ( - - Bulk Data Transfer - + Bulk Data Transfer + + This section explains how to download data in bulk from raw and processed datasets. Processed datasets have + separate download directories in Globus or dbGaP, distinct from the raw dataset. + diff --git a/context/app/static/js/components/detailPage/CollectionsSection/CollectionsSection.tsx b/context/app/static/js/components/detailPage/CollectionsSection/CollectionsSection.tsx index 309b5a3cf3..b13a6e912a 100644 --- a/context/app/static/js/components/detailPage/CollectionsSection/CollectionsSection.tsx +++ b/context/app/static/js/components/detailPage/CollectionsSection/CollectionsSection.tsx @@ -2,10 +2,10 @@ import React from 'react'; import SectionHeader from 'js/shared-styles/sections/SectionHeader'; import PanelList from 'js/shared-styles/panels/PanelList'; -import { useFlaskDataContext } from 'js/components/Contexts'; import { DetailPageSection } from 'js/components/detailPage/style'; import { buildCollectionsPanelsProps } from 'js/pages/Collections/utils'; import { CollectionHit } from 'js/pages/Collections/types'; +import { SectionDescription } from '../ProcessedData/ProcessedDataset/SectionDescription'; interface CollectionsSectionProps { collectionsData: CollectionHit[]; @@ -14,15 +14,13 @@ interface CollectionsSectionProps { function CollectionsSection({ collectionsData }: CollectionsSectionProps) { const panelsProps = buildCollectionsPanelsProps(collectionsData); - const { - entity: { entity_type }, - } = useFlaskDataContext(); - return ( - - Collections - + Collections + + 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. + ); diff --git a/context/app/static/js/components/detailPage/DatasetRelationships/nodeTypes.tsx b/context/app/static/js/components/detailPage/DatasetRelationships/nodeTypes.tsx index 48762cbc60..3af481f18c 100644 --- a/context/app/static/js/components/detailPage/DatasetRelationships/nodeTypes.tsx +++ b/context/app/static/js/components/detailPage/DatasetRelationships/nodeTypes.tsx @@ -6,6 +6,7 @@ import Typography from '@mui/material/Typography'; import { AccountTreeRounded, ExtensionRounded, SvgIconComponent } from '@mui/icons-material'; import Skeleton from '@mui/material/Skeleton'; import { Box } from '@mui/system'; +import { formatSectionHash } from 'js/shared-styles/sections/TableOfContents/utils'; import StatusIcon from '../StatusIcon'; import { usePipelineInfo } from './hooks'; @@ -126,7 +127,7 @@ function ProcessedDatasetNode({ data }: NodeProps) { ) { rounded target icon={nodeIcons.componentDataset} - href={`#${data.name}-section`} + href={formatSectionHash(`#section-${data.name}`)} bgColor={nodeColors.componentDataset} {...data} > diff --git a/context/app/static/js/components/detailPage/DetailLayout/DetailLayout.tsx b/context/app/static/js/components/detailPage/DetailLayout/DetailLayout.tsx index f13361b9ae..5797219e5c 100644 --- a/context/app/static/js/components/detailPage/DetailLayout/DetailLayout.tsx +++ b/context/app/static/js/components/detailPage/DetailLayout/DetailLayout.tsx @@ -5,7 +5,7 @@ import Stack from '@mui/material/Stack'; import useEntityStore, { savedAlertStatus, editedAlertStatus, EntityStore } from 'js/stores/useEntityStore'; import TableOfContents from 'js/shared-styles/sections/TableOfContents'; import { TableOfContentsItems } from 'js/shared-styles/sections/TableOfContents/types'; -import { leftRouteBoundaryID } from 'js/components/Routes/Route/Route'; +import { leftRouteBoundaryID, rightRouteBoundaryID } from 'js/components/Routes/Route/Route'; import { SectionOrder, getSections } from 'js/shared-styles/sections/TableOfContents/utils'; import { StyledAlert } from './style'; @@ -33,6 +33,14 @@ function TableOfContentsPortal({ items, isLoading = false }: { items: TableOfCon ); } +export function HelperPanelPortal({ children }: PropsWithChildren) { + const element = document.getElementById(rightRouteBoundaryID); + if (!element) { + return null; + } + return createPortal(children, element); +} + function DetailAlert() { const { shouldDisplaySavedOrEditedAlert, setShouldDisplaySavedOrEditedAlert } = useEntityStore(entityStoreSelector); diff --git a/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/HelperPanel.tsx b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/HelperPanel.tsx new file mode 100644 index 0000000000..8b4f5bcb02 --- /dev/null +++ b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/HelperPanel.tsx @@ -0,0 +1,138 @@ +import React, { PropsWithChildren } from 'react'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { formatDate } from 'date-fns/format'; +import Divider from '@mui/material/Divider'; +import Box from '@mui/system/Box'; +import { useIsDesktop } from 'js/hooks/media-queries'; +import SchemaRounded from '@mui/icons-material/SchemaRounded'; +import { WorkspacesIcon } from 'js/shared-styles/icons'; +import { CloudDownloadRounded } from '@mui/icons-material'; +import { useAppContext } from 'js/components/Contexts'; +import { formatSectionHash } from 'js/shared-styles/sections/TableOfContents/utils'; +import { HelperPanelPortal } from '../../DetailLayout/DetailLayout'; +import useProcessedDataStore from '../store'; +import StatusIcon from '../../StatusIcon'; +import { getDateLabelAndValue } from '../../utils'; +import { HelperPanelButton } from './styles'; + +function useCurrentDataset() { + return useProcessedDataStore((state) => state.currentDataset); +} + +function HelperPanelHeader() { + const currentDataset = useCurrentDataset(); + return ( + + + {currentDataset?.hubmap_id} + + ); +} + +function HelperPanelStatus() { + const currentDataset = useCurrentDataset(); + if (!currentDataset) { + return null; + } + return ( + + + {currentDataset.status} + + ); +} + +interface HelperPanelBodyItemProps extends PropsWithChildren { + label: string; + noWrap?: boolean; +} + +const noWrapStyles = { + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', +}; + +function HelperPanelBodyItem({ label, children, noWrap }: HelperPanelBodyItemProps) { + const valueStyles = noWrap ? noWrapStyles : {}; + return ( + + {label} + + {children} + + + ); +} + +function HelperPanelBody() { + const currentDataset = useCurrentDataset(); + if (!currentDataset) { + return null; + } + const [dateLabel, date] = getDateLabelAndValue(currentDataset); + return ( + <> + {currentDataset.title && {currentDataset.title}} + {currentDataset.description && ( + + {currentDataset.description} + + )} + {currentDataset.pipeline} + {currentDataset.group_name} + {date && formatDate(date, 'yyyy-MM-dd')} + + ); +} + +function HelperPanelActions() { + const currentDataset = useCurrentDataset(); + // TODO: Add workspace actions/dropdown menu + const { isWorkspacesUser } = useAppContext(); + return ( + <> + {isWorkspacesUser && }>Workspace} + } + href={`#bulk-data-transfer?bulk-data=${currentDataset?.hubmap_id}`} + > + Bulk Download + + + ); +} + +export default function HelperPanel() { + const currentDataset = useCurrentDataset(); + // const panelOffset = useProcessedDataStore((state) => state.currentDatasetOffset); + const isDesktop = useIsDesktop(); + if (!currentDataset || !isDesktop) { + return null; + } + return ( + + + + + + + + + + + + ); +} diff --git a/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/index.ts b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/index.ts new file mode 100644 index 0000000000..e1fb356fbc --- /dev/null +++ b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/index.ts @@ -0,0 +1,3 @@ +import HelperPanel from './HelperPanel'; + +export default HelperPanel; diff --git a/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/styles.tsx b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/styles.tsx new file mode 100644 index 0000000000..75b5b8568d --- /dev/null +++ b/context/app/static/js/components/detailPage/ProcessedData/HelperPanel/styles.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { styled } from '@mui/material/styles'; +import Button, { ButtonProps } from '@mui/material/Button'; + +export const HelperPanelButton = styled((props: ButtonProps) =>