From 4d880663397ad66429c30f35bc3c590d533f0884 Mon Sep 17 00:00:00 2001 From: Nikolay Akhmetov Date: Thu, 1 Aug 2024 14:17:27 -0400 Subject: [PATCH] add helper panel, add publication/creation date logic, add tooltips to analysis details, add section descriptions, ensure hash links work with `document.querySelector`, add preliminary logic for scrolling to lazy loaded pieces, --- .../AnalysisDetails/AnalysisDetails.tsx | 16 +- .../AnalysisDetails/AnalysisDetailsList.tsx | 8 +- .../BulkDataTransferSection.tsx | 16 +- .../CollectionsSection/CollectionsSection.tsx | 14 +- .../DatasetRelationships/nodeTypes.tsx | 5 +- .../detailPage/DetailLayout/DetailLayout.tsx | 10 +- .../ProcessedData/HelperPanel/HelperPanel.tsx | 138 ++++++++++++++++++ .../ProcessedData/HelperPanel/index.ts | 3 + .../ProcessedData/HelperPanel/styles.tsx | 12 ++ .../ProcessedData/ProcessedData.tsx | 29 ++-- .../ProcessedDataset/ProcessedDataset.tsx | 81 ++++++---- .../ProcessedDatasetAccordion.tsx | 3 +- .../ProcessedDataset/SectionDescription.tsx | 27 ++++ .../ProcessedDataset/Subsection.tsx | 3 +- .../detailPage/ProcessedData/store.ts | 39 +++++ .../provenance/ProvSection/ProvSection.tsx | 6 + .../static/js/components/detailPage/utils.ts | 20 +++ .../app/static/js/pages/Dataset/Dataset.tsx | 3 +- context/app/static/js/pages/Dataset/hooks.ts | 39 ++++- .../sections/TableOfContents/utils.ts | 13 +- .../static/js/shared-styles/surfaces/index.js | 8 - .../static/js/shared-styles/surfaces/index.ts | 8 + 22 files changed, 416 insertions(+), 85 deletions(-) create mode 100644 context/app/static/js/components/detailPage/ProcessedData/HelperPanel/HelperPanel.tsx create mode 100644 context/app/static/js/components/detailPage/ProcessedData/HelperPanel/index.ts create mode 100644 context/app/static/js/components/detailPage/ProcessedData/HelperPanel/styles.tsx create mode 100644 context/app/static/js/components/detailPage/ProcessedData/ProcessedDataset/SectionDescription.tsx create mode 100644 context/app/static/js/components/detailPage/ProcessedData/store.ts delete mode 100644 context/app/static/js/shared-styles/surfaces/index.js create mode 100644 context/app/static/js/shared-styles/surfaces/index.ts 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) =>