Skip to content
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

Austenem/CAT-983 MVP Bulk File Download #3604

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
99c1736
add modal entry points
austenem Oct 30, 2024
2d11c5d
add modal infrastructure
austenem Oct 30, 2024
2bd6009
add form options
austenem Oct 31, 2024
f50fb2e
continue form logic
austenem Oct 31, 2024
a368f43
fix loading issue
austenem Oct 31, 2024
5ccddb5
pass in datasets from publication page
austenem Nov 1, 2024
d616b20
implement manifest functionality
austenem Nov 1, 2024
c139813
add filtering by dataset type
austenem Nov 1, 2024
c51dc1e
add metadata download functionality
austenem Nov 1, 2024
cdd6d69
integrate with search page
austenem Nov 1, 2024
47099a3
switch to uuid query
austenem Nov 4, 2024
f4e7f15
add info to dialog
austenem Nov 4, 2024
7eb079c
add advanced selections accordion
austenem Nov 4, 2024
e6ec0ba
use download button component
austenem Nov 5, 2024
eb6c5dd
implement checkboxes and more form functionality
austenem Nov 7, 2024
34a14aa
fix select all option
austenem Nov 7, 2024
73b15c6
add alert
austenem Nov 7, 2024
fce7a12
add default errors
austenem Nov 7, 2024
8fc7d14
fix query response time
austenem Nov 7, 2024
6ff2a12
add download selection section
austenem Nov 7, 2024
3b60355
fix error issue
austenem Nov 7, 2024
26b0356
add toggle component
austenem Nov 7, 2024
fb38840
add protected datasets logic
austenem Nov 8, 2024
37f1776
separate protected dataset section
austenem Nov 8, 2024
5a00860
add dataset counts
austenem Nov 8, 2024
38591b4
update language and styles
austenem Nov 8, 2024
c3cbcc9
update download functions
austenem Nov 8, 2024
0cfbedb
adjust switch
austenem Nov 8, 2024
b3f362c
select all datasets functionality
austenem Nov 12, 2024
a7f4703
update error toasts
austenem Nov 12, 2024
3fa2492
add success toasts and update tsv download
austenem Nov 12, 2024
c9264fc
update file downloads
austenem Nov 12, 2024
1b044af
consolidate files and toasts
austenem Nov 12, 2024
4fb7c15
fix protected datasets bug
austenem Nov 13, 2024
bb0e905
add retry function option
austenem Nov 13, 2024
61559df
clean up hooks and dialog
austenem Nov 13, 2024
c0c156e
last round of cleanup
austenem Nov 13, 2024
2a1538a
add changelog
austenem Nov 13, 2024
8b30019
first round of review changes
austenem Nov 13, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG-bulk-download-mvp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add dialog option to the Search, Publication, and Collection pages that generates a download manifest for files from selected datasets.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { ButtonProps } from '@mui/material/Button';
import SvgIcon from '@mui/material/SvgIcon';
import Download from '@mui/icons-material/Download';
import { useBulkDownloadDialog } from 'js/components/bulkDownload/hooks';
import BulkDownloadDialog from 'js/components/bulkDownload/BulkDownloadDialog';
import { WhiteBackgroundIconTooltipButton } from 'js/shared-styles/buttons';

interface BulkDownloadButtonProps extends ButtonProps {
tooltip: string;
uuids: Set<string>;
deselectRows?: (uuids: string[]) => void;
}
function BulkDownloadButton({ tooltip, uuids, deselectRows, ...rest }: BulkDownloadButtonProps) {
const { openDialog } = useBulkDownloadDialog();

return (
<>
<WhiteBackgroundIconTooltipButton tooltip={tooltip} onClick={() => openDialog(uuids)} {...rest}>
<SvgIcon color="primary" component={Download} />
</WhiteBackgroundIconTooltipButton>
<BulkDownloadDialog deselectRows={deselectRows} />
</>
);
}

export default BulkDownloadButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BulkDownloadButton from './BulkDownloadButton';

export default BulkDownloadButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import BulkDownloadButton from 'js/components/bulkDownload/BulkDownloadButton/BulkDownloadButton';
import { useSelectableTableStore } from 'js/shared-styles/tables/SelectableTableProvider';

const tooltip =
'Bulk download files for selected datasets. If no datasets are selected, all datasets given the current filters will be selected.';

interface BulkDownloadButtonFromSearchProps {
type: string;
allResultsUUIDs: Set<string>;
}
function BulkDownloadButtonFromSearch({ type, allResultsUUIDs }: BulkDownloadButtonFromSearchProps) {
const { selectedRows, deselectRows } = useSelectableTableStore();

const isDatasetSearch = type.toLowerCase() === 'dataset';
if (!isDatasetSearch) {
return null;
}

return (
<BulkDownloadButton
tooltip={tooltip}
deselectRows={deselectRows}
uuids={selectedRows.size > 0 ? selectedRows : allResultsUUIDs}
sx={(theme) => ({ margin: theme.spacing(0, 1) })}
/>
);
}

export default BulkDownloadButtonFromSearch;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BulkDownloadButtonFromSearch from './BulkDownloadButtonFromSearch';

export default BulkDownloadButtonFromSearch;
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import React from 'react';
import { Control } from 'react-hook-form';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import Skeleton from '@mui/material/Skeleton';
import Typography from '@mui/material/Typography';

import { LINKS, PAGES } from 'js/components/bulkDownload/constants';
import { BulkDownloadFormTypes, useBulkDownloadDialog } from 'js/components/bulkDownload/hooks';
import RemoveProtectedDatasetsFormField from 'js/components/workspaces/RemoveProtectedDatasetsFormField';
import BulkDownloadOptionsField from 'js/components/bulkDownload/BulkDownloadOptionsField';
import BulkDownloadMetadataField from 'js/components/bulkDownload/BulkDownloadMetadataField';
import { DatasetAccessLevelHits } from 'js/hooks/useProtectedDatasets';
import SummaryPaper from 'js/shared-styles/sections/SectionPaper';
import DialogModal from 'js/shared-styles/DialogModal';
import { SectionDescription } from 'js/shared-styles/sections/SectionDescription';
import Step from 'js/shared-styles/surfaces/Step';
import { OutboundLink } from 'js/shared-styles/Links';
import RelevantPagesSection from 'js/shared-styles/sections/RelevantPagesSection';
import LabelledSectionText from 'js/shared-styles/sections/LabelledSectionText';
import { Alert } from 'js/shared-styles/alerts';
import ErrorOrWarningMessages from 'js/shared-styles/alerts/ErrorOrWarningMessages';

function DownloadDescription() {
return (
<SectionDescription>
<Stack spacing={2}>
<Box>
Choose download options to bulk download files from your selected datasets. Your selection of files will
generate a manifest file, which can be used with the{' '}
<OutboundLink href={LINKS.documentation}>HuBMAP Command Line Transfer (CLT)</OutboundLink> tool for
downloading. An option to download a tsv file of the metadata is also available.
</Box>
<Box>
To download the files included in the manifest file,{' '}
<OutboundLink href={LINKS.installation}>install the HuBMAP CLT</OutboundLink> (if not already installed) and
follow <OutboundLink href={LINKS.documentation}>instructions</OutboundLink> for how to use it with the
manifest file.
{/* TODO: uncomment once tutorial is created */}
{/* A <OutboundLink href={LINKS.tutorial}>tutorial</OutboundLink> is available to guide you through
the entire process. */}
</Box>
<RelevantPagesSection pages={PAGES} />
</Stack>
</SectionDescription>
);
}

interface ProtectedDatasetsSectionProps {
control: Control<BulkDownloadFormTypes>;
errorMessages: string[];
protectedHubmapIds: string;
protectedRows: DatasetAccessLevelHits;
removeProtectedDatasets: () => void;
}
function ProtectedDatasetsSection({
control,
errorMessages,
protectedHubmapIds,
protectedRows,
removeProtectedDatasets,
}: ProtectedDatasetsSectionProps) {
if (errorMessages.length === 0) {
return null;
}

return (
<Stack paddingY={1}>
<ErrorOrWarningMessages errorMessages={errorMessages} />
<RemoveProtectedDatasetsFormField
control={control}
protectedHubmapIds={protectedHubmapIds}
removeProtectedDatasets={removeProtectedDatasets}
protectedRows={protectedRows}
/>
</Stack>
);
}

function DownloadOptionsDescription() {
return (
<SectionDescription>
<Stack spacing={2}>
<Box>Select raw and/or processed files to download.</Box>
<LabelledSectionText label="Raw Data" spacing={1}>
Raw data consists of files as originally submitted by the data submitters. Individuals files for raw data
cannot be previewed or selected for the manifest download. You must download all raw files, and the total
download size cannot be estimated.
</LabelledSectionText>
<LabelledSectionText label="Processed Data" spacing={1}>
Processed data includes files associated with data generated by HuBMAP using uniform processing pipelines or
by an external processing approach. Only files centrally processed by HuBMAP are available for individual
selection, which can be done in the Advanced Selections section below.
</LabelledSectionText>
</Stack>
</SectionDescription>
);
}

interface DownloadOptionsSectionProps {
control: Control<BulkDownloadFormTypes>;
downloadOptions: {
key: string;
label: string;
}[];
isLoading: boolean;
errorMessages: string[];
protectedHubmapIds: string;
protectedRows: DatasetAccessLevelHits;
removeProtectedDatasets: () => void;
}
function DownloadOptionsSection({
control,
downloadOptions,
isLoading,
errorMessages,
protectedHubmapIds,
protectedRows,
removeProtectedDatasets,
}: DownloadOptionsSectionProps) {
if (isLoading) {
return (
<>
<Skeleton variant="rectangular" height={50} />
<Skeleton variant="rectangular" height={300} />
<Skeleton variant="rectangular" height={200} />
</>
);
}

if (downloadOptions.length === 0) {
return (
<Box paddingTop={1}>
<Alert severity="warning">
<Typography>Files are not available for any of the selected datasets.</Typography>
</Alert>
</Box>
);
}

return (
<Box>
<Step title="Download Options" hideRequiredText>
<ProtectedDatasetsSection
control={control}
errorMessages={errorMessages}
protectedHubmapIds={protectedHubmapIds}
protectedRows={protectedRows}
removeProtectedDatasets={removeProtectedDatasets}
/>
<DownloadOptionsDescription />
<SummaryPaper>
<Stack direction="column" spacing={2}>
<BulkDownloadOptionsField control={control} name="bulkDownloadOptions" />
<BulkDownloadMetadataField control={control} name="bulkDownloadMetadata" />
</Stack>
</SummaryPaper>
</Step>
</Box>
);
}

const formId = 'bulk-download-form';

interface BulkDownloadDialogProps {
deselectRows?: (uuids: string[]) => void;
}
function BulkDownloadDialog({ deselectRows }: BulkDownloadDialogProps) {
const {
handleSubmit,
onSubmit,
handleClose,
isOpen,
errors,
control,
isLoading,
downloadOptions,
errorMessages,
...rest
} = useBulkDownloadDialog(deselectRows);

return (
<DialogModal
title="Bulk Download Files"
maxWidth="lg"
content={
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<form id={formId} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={2}>
<DownloadDescription />
<DownloadOptionsSection
control={control}
downloadOptions={downloadOptions}
isLoading={isLoading}
errorMessages={errorMessages}
{...rest}
/>
</Stack>
</form>
}
isOpen={isOpen}
handleClose={handleClose}
actions={
<Stack direction="row" spacing={2} alignItems="end">
<Button type="button" onClick={handleClose}>
Cancel
</Button>
<Button
type="submit"
variant="contained"
form={formId}
disabled={Object.keys(errors).length > 0 || errorMessages.length > 0}
>
Generate Download Manifest
</Button>
</Stack>
}
withCloseButton
/>
);
}

export default BulkDownloadDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BulkDownloadDialog from './BulkDownloadDialog';

export default BulkDownloadDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';
import Stack from '@mui/material/Stack';
import { PrimarySwitch } from 'js/shared-styles/switches';
import { StyledFormLabel } from 'js/components/bulkDownload/style';

type BulkDownloadMetadataFieldProps<FormType extends FieldValues> = Pick<
UseControllerProps<FormType>,
'name' | 'control'
>;
function BulkDownloadMetadataField<FormType extends FieldValues>({
control,
name,
}: BulkDownloadMetadataFieldProps<FormType>) {
const { field } = useController({
name,
control,
});

return (
<Stack>
<StyledFormLabel id="bulk-download-metadata">Download Metadata File (TSV)</StyledFormLabel>
<PrimarySwitch
checked={field.value}
onChange={(e) => field.onChange(!!e.target.checked)}
inputProps={{ 'aria-labelledby': 'bulk-download-metadata' }}
sx={(theme) => ({ marginLeft: theme.spacing(-1) })}
/>
</Stack>
);
}

export default BulkDownloadMetadataField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BulkDownloadMetadataField from './BulkDownloadMetadataField';

export default BulkDownloadMetadataField;
Loading
Loading