Skip to content

Commit

Permalink
[MNT] Refactored the DownloadResultButton (#396)
Browse files Browse the repository at this point in the history
* Implemented splitbutton for `DownloadResultButton`

* Updated `GetDataDialog` component

* Updated `ResultContainer` component

* Updated `ResultsTSV` e2e test

* Updated docker command

* [ENH] Standardized query result files (#398)

* Standardized query result files

* Addressed the PR review comments

* Addressed PR review comments
  • Loading branch information
rmanaem authored Dec 13, 2024
1 parent 8277948 commit 121c026
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 158 deletions.
57 changes: 30 additions & 27 deletions cypress/component/DownloadResultButton.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import DownloadResultButton from '../../src/components/DownloadResultButton';

const props = {
identifier: 'test',
disabled: false,
handleClick: () => {},
};

describe('DownloadResultButton', () => {
it('Displays an enabled MUI Button with the identifier passed as prop', () => {
cy.mount(
<DownloadResultButton
identifier={props.identifier}
disabled={props.disabled}
handleClick={props.handleClick}
/>
cy.mount(<DownloadResultButton disabled={props.disabled} handleClick={props.handleClick} />);
cy.get('[data-cy="download-results-button"]').should('be.visible');
cy.get('[data-cy="download-results-button"]').should(
'contain',
'Download selected query results'
);
cy.get('[data-cy="test-download-results-button"]').should('be.visible');
cy.get('[data-cy="test-download-results-button"]').should('contain', 'Download test result');
cy.get('[data-cy="test-download-results-button"]').should('not.be', 'disabled');
cy.get('[data-cy="download-results-button"]').should('not.be', 'disabled');
});
it('Displays a disabled MUI Button and a tooltip when the button is hovered over', () => {
cy.mount(
<DownloadResultButton
identifier={props.identifier}
disabled
handleClick={props.handleClick}
/>
it('Fires the handleClick event handler with the appropriate payload when the button is clicked', () => {
const handleClickSpy = cy.spy().as('handleClickSpy');
cy.mount(<DownloadResultButton disabled={props.disabled} handleClick={handleClickSpy} />);
cy.get('[data-cy="download-results-dropdown-button"]').click();
cy.contains('URIs').click();
cy.get('[data-cy="download-results-button"]').should(
'contain',
'Download selected query results with URIs'
);
cy.get('[data-cy="test-download-results-button"]').trigger('mouseover', { force: true });
cy.get('[data-cy="download-results-button"]').click();
cy.get('@handleClickSpy').should('have.been.calledWith', 1);
cy.get('[data-cy="download-results-dropdown-button"]').click();
cy.contains(/results$/).click();
cy.get('[data-cy="download-results-button"]')
.invoke('text')
.should('match', /^Download selected query results$/);
cy.get('[data-cy="download-results-button"]').click();
cy.get('@handleClickSpy').should('have.been.calledWith', 0);
});
it('Displays a disabled MUI Button and a tooltip when the button is hovered over', () => {
cy.mount(<DownloadResultButton disabled handleClick={props.handleClick} />);
cy.get('[data-cy="download-results-button"]').trigger('mouseover', { force: true });
cy.get('.MuiTooltip-tooltip').should('contain', 'Please select at least one dataset');
});
it('Fires the handleClick event handler with the appropriate payload when the button is clicked', () => {
const handleClickSpy = cy.spy().as('handleClickSpy');
cy.mount(
<DownloadResultButton
identifier={props.identifier}
disabled={props.disabled}
handleClick={handleClickSpy}
/>
);
cy.get('[data-cy="test-download-results-button"]').click();
cy.get('@handleClickSpy').should('have.been.calledWith', 'test');
cy.mount(<DownloadResultButton disabled={props.disabled} handleClick={handleClickSpy} />);
cy.get('[data-cy="download-results-button"]').click();
cy.get('@handleClickSpy').should('have.been.calledWith', 0);
});
});
44 changes: 3 additions & 41 deletions cypress/component/GetDataDialog.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,25 @@ import GetDataDialog from '../../src/components/GetDataDialog';
const props = {
open: true,
onClose: () => {},
disableDownloadResultsButton: false,
handleDownloadResultButtonClick: () => {},
};

describe('GetDataDialog', () => {
it('Displays a MUI Diaglog with content', () => {
cy.mount(
<GetDataDialog
open={props.open}
onClose={props.onClose}
disableDownloadResultsButton={props.disableDownloadResultsButton}
handleDownloadResultButtonClick={props.handleDownloadResultButtonClick}
/>
);
cy.mount(<GetDataDialog open={props.open} onClose={props.onClose} />);
cy.get('[data-cy="get-data-dialog"]').should('be.visible');
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').should('be.visible');
cy.get('[data-cy="get-data-dialog"] p').should(
'contain',
'The above command currently only gets data for DataLad datasets'
);
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').should('be.visible');
});
it("Doesn't display the dialog when open prop is set to false", () => {
cy.mount(
<GetDataDialog
open={false}
onClose={props.onClose}
disableDownloadResultsButton={props.disableDownloadResultsButton}
handleDownloadResultButtonClick={props.handleDownloadResultButtonClick}
/>
);
cy.mount(<GetDataDialog open={false} onClose={props.onClose} />);
cy.get('[data-cy="get-data-dialog"]').should('not.exist');
});
it('Fires onClose event handler when the close button is clicked', () => {
const onCloseSpy = cy.spy().as('onCloseSpy');
cy.mount(
<GetDataDialog
open={props.open}
onClose={onCloseSpy}
disableDownloadResultsButton={props.disableDownloadResultsButton}
handleDownloadResultButtonClick={props.handleDownloadResultButtonClick}
/>
);
cy.mount(<GetDataDialog open={props.open} onClose={onCloseSpy} />);
cy.get('[data-cy="get-data-dialog-close-button"]').click();
cy.get('@onCloseSpy').should('have.been.called');
});
it('Fires handleDownloadResultButtonClick event handler when the download button is clicked', () => {
const handleDownloadResultButtonClickSpy = cy.spy().as('handleDownloadResultButtonClickSpy');
cy.mount(
<GetDataDialog
open={props.open}
onClose={props.onClose}
disableDownloadResultsButton={props.disableDownloadResultsButton}
handleDownloadResultButtonClick={handleDownloadResultButtonClickSpy}
/>
);
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').click();
cy.get('@handleDownloadResultButtonClickSpy').should('have.been.called');
});
});
8 changes: 2 additions & 6 deletions cypress/component/ResultContainer.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,15 @@ describe('ResultContainer', () => {
cy.get('[data-cy="summary-stats"]')
.should('be.visible')
.should('contain', 'Summary stats: 2 datasets, 4 subjects');
cy.get('[data-cy="cohort-participant-download-results-button"]')
.should('be.visible')
.should('be.disabled');
cy.get('[data-cy="download-results-button"]').should('be.visible').should('be.disabled');
cy.get('[data-cy="how-to-get-data-dialog-button"]').should('be.visible');
});
it('Selecting a dataset should enable the download result button', () => {
cy.mount(
<ResultContainer response={protectedResponse2} assessmentOptions={[]} diagnosisOptions={[]} />
);
cy.get('[data-cy="card-https://someportal.org/datasets/ds0001-checkbox"] input').check();
cy.get('[data-cy="cohort-participant-download-results-button"]')
.should('be.visible')
.should('not.be.disabled');
cy.get('[data-cy="download-results-button"]').should('be.visible').should('not.be.disabled');
});
it('Selecting/unselecting select all datasets checkbox should check/uncheck all dataset cards', () => {
cy.mount(
Expand Down
31 changes: 14 additions & 17 deletions cypress/e2e/ResultsTSV.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ describe('Results TSV', () => {
cy.get('[data-cy="submit-query-button"]').click();
cy.wait('@call');
cy.get('[data-cy="select-all-checkbox"]').find('input').check();
cy.get('[data-cy="how-to-get-data-dialog-button"]').click();
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').click();
cy.readFile('cypress/downloads/cohort-participant-machine-results.tsv').should(
cy.get('[data-cy="download-results-dropdown-button"]').click();
cy.contains('URIs').click();
cy.get('[data-cy="download-results-button"]').click();
cy.readFile('cypress/downloads/neurobagel-query-results-with-URIs.tsv').should(
'contain',
'some cool name'
);
Expand All @@ -32,24 +33,22 @@ describe('Results TSV', () => {
cy.get('[data-cy="submit-query-button"]').click();
cy.wait('@call');
cy.get('[data-cy="select-all-checkbox"]').find('input').check();
cy.get('[data-cy="cohort-participant-download-results-button"]').click();
cy.readFile('cypress/downloads/cohort-participant-results.tsv').then((fileContent) => {
cy.get('[data-cy="download-results-button"]').click();
cy.readFile('cypress/downloads/neurobagel-query-results.tsv').then((fileContent) => {
expect(fileContent).to.match(/^DatasetName/);
});
cy.get('[data-cy="how-to-get-data-dialog-button"]').click();
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').click();
cy.readFile('cypress/downloads/cohort-participant-machine-results.tsv').then((fileContent) => {
cy.get('[data-cy="download-results-dropdown-button"]').click();
cy.contains('URIs').click();
cy.readFile('cypress/downloads/neurobagel-query-results-with-URIs.tsv').then((fileContent) => {
expect(fileContent).to.match(/^DatasetName/);
});
});
it('Checks whether the protected and unprotected datasets are correctly identified', () => {
cy.get('[data-cy="submit-query-button"]').click();
cy.wait('@call');
cy.get('[data-cy="select-all-checkbox"]').find('input').check();
cy.get('[data-cy="cohort-participant-download-results-button"]').click();
cy.get('[data-cy="how-to-get-data-dialog-button"]').click();
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').click();
cy.readFile('cypress/downloads/cohort-participant-results.tsv').then((fileContent) => {
cy.get('[data-cy="download-results-button"]').click();
cy.readFile('cypress/downloads/neurobagel-query-results.tsv').then((fileContent) => {
const rows = fileContent.split('\n');

const datasetProtected = rows[1];
Expand All @@ -61,7 +60,7 @@ describe('Results TSV', () => {
});
});
describe('Unprotected response', () => {
it('Checks whether the rows in the participant.tsv file generated according to session_type', () => {
it.only('Checks whether the rows in the participant.tsv file generated according to session_type', () => {
cy.intercept('query?*', unprotectedResponse).as('call');
cy.intercept(
{
Expand All @@ -88,11 +87,9 @@ describe('Unprotected response', () => {
cy.get('[data-cy="submit-query-button"]').click();
cy.wait('@call');
cy.get('[data-cy="select-all-checkbox"]').find('input').check();
cy.get('[data-cy="cohort-participant-download-results-button"]').click();
cy.get('[data-cy="how-to-get-data-dialog-button"]').click();
cy.get('[data-cy="cohort-participant-machine-download-results-button"]').click();
cy.get('[data-cy="download-results-button"]').click();

cy.readFile('cypress/downloads/cohort-participant-results.tsv').then((fileContent) => {
cy.readFile('cypress/downloads/neurobagel-query-results.tsv').then((fileContent) => {
const rows = fileContent.split('\n');

const phenotypicSession = rows[1];
Expand Down
87 changes: 73 additions & 14 deletions src/components/DownloadResultButton.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,84 @@
import DownloadIcon from '@mui/icons-material/Download';
import { Button, Tooltip, Typography } from '@mui/material';
import { useState, useRef } from 'react';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import ButtonGroup from '@mui/material/ButtonGroup';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';

const options = ['Download selected query results', 'Download selected query results with URIs'];

function DownloadResultButton({
identifier,
disabled,
handleClick,
}: {
identifier: string;
disabled: boolean;
handleClick: (identifier: string) => void;
handleClick: (index: number) => void;
}) {
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLDivElement>(null);
const [selectedIndex, setSelectedIndex] = useState(0);

const handleMenuItemClick = (index: number) => {
setSelectedIndex(index);
setOpen(false);
};

const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};

const handleClose = (event: Event) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}

setOpen(false);
};

const button = (
<Button
data-cy={`${identifier}-download-results-button`}
variant="contained"
startIcon={<DownloadIcon />}
onClick={() => handleClick(identifier)}
disabled={disabled}
>
Download {identifier.split('-').join(' ')} result
</Button>
<>
<ButtonGroup disabled={disabled} variant="contained" ref={anchorRef}>
<Button onClick={() => handleClick(selectedIndex)} data-cy="download-results-button">
{options[selectedIndex]}
</Button>
<Button data-cy="download-results-dropdown-button" size="small" onClick={handleToggle}>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper sx={{ zIndex: 1 }} open={open} anchorEl={anchorRef.current} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
// eslint-disable-next-line react/jsx-props-no-spreading
{...TransitionProps}
style={{
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu" autoFocusItem>
{options.map((option, index) => (
<MenuItem
key={option}
selected={index === selectedIndex}
onClick={() => handleMenuItemClick(index)}
>
{option}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);

return disabled ? (
Expand Down
38 changes: 8 additions & 30 deletions src/components/GetDataDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,11 @@ import {
} from '@mui/material';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { useTheme } from '@mui/material/styles';
import DownloadResultButton from './DownloadResultButton';
import NBTheme from '../theme';

function GetDataDialog({
open,
onClose,
disableDownloadResultsButton,
handleDownloadResultButtonClick,
}: {
open: boolean;
onClose: () => void;
disableDownloadResultsButton: boolean;
handleDownloadResultButtonClick: (identifier: string) => void;
}) {
function GetDataDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
const DOCKER_RUN_COMMAND =
'docker run -t -v $(pwd):/data neurobagel/dataget:latest /data/cohort-participant-machine-results.tsv /data/output';
'docker run -t -v $(pwd):/data neurobagel/dataget:latest /data/neurobagel-query-results-with-URIs.tsv /data/output';
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

Expand Down Expand Up @@ -56,25 +45,14 @@ function GetDataDialog({
<Typography variant="h6" className="font-bold">
Results file for data access or programmatic use
</Typography>
Below is a machine-optimized version of your selected query results, which we recommend
using as input to scripts for downloading the data of matching subjects to your local
filesystem. This file also contains URIs instead of descriptive labels, making it ideal
for integration with other tools for linked or structured data:
<div className="m-4 flex justify-center">
<DownloadResultButton
identifier="cohort-participant-machine"
disabled={disableDownloadResultsButton}
handleClick={(identifier) => handleDownloadResultButtonClick(identifier)}
/>
</div>
<Typography variant="h6" className="font-bold">
Download matching results from DataLad datasets
</Typography>
We have a helper tool to automatically download matching subjects from datasets available
through DataLad. To do so:
Download the query results file with URIs to use as input to scripts for downloading the
data of matching subjects to your local filesystem. This file contains URIs instead of
descriptive labels making it ideal for integration with other tools for linked or
structured data. We have a helper tool to automatically download matching subjects from
datasets available through DataLad. To do so:
<ol>
<li>Select at least one dataset</li>
<li>Download the cohort results for machines using the above button</li>
<li>Download the results file with URIs using the dropdown download button</li>
<li>Change directory to the location of the downloaded TSV</li>
<li>Copy and run the command below</li>
</ol>
Expand Down
Loading

0 comments on commit 121c026

Please sign in to comment.