Skip to content

Commit

Permalink
Merge pull request hms-dbmi-cellenics#898 from biomage-org/cell-level…
Browse files Browse the repository at this point in the history
…-meta

[Cell-level metadata] Update the Add metadata select
  • Loading branch information
StefanBabukov authored Oct 4, 2023
2 parents e36cc58 + b1f0afe commit 7c39c16
Show file tree
Hide file tree
Showing 18 changed files with 599 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
render, screen,
} from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import handleError from 'utils/http/handleError';
import readFileToString from 'utils/upload/readFileToString';

import componentFactory from '__test__/test-utils/testComponentFactory';
import CellLevelUploadModal from 'components/data-management/CellLevelUploadModal';
import { dropFilesIntoDropzone } from '__test__/test-utils/rtlHelpers';

jest.mock('utils/upload/readFileToString');
jest.mock('utils/http/handleError');

const defaultProps = {
onUpload: jest.fn(),
onCancel: jest.fn(),
};

const CellLevelModalFactory = componentFactory(CellLevelUploadModal, defaultProps);

const renderCellLevelUploadModal = async (customProps = {}) => {
act(() => {
render(CellLevelModalFactory(customProps));
});
};

describe('CellLevelUploadModal', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('shows required components required to upload the cell level file', async () => {
await renderCellLevelUploadModal();

expect(screen.getByText(/File Upload/i)).toBeInTheDocument();

const uploadButtonText = screen.getAllByText(/Upload/i).pop();
const uploadButton = uploadButtonText.closest('button');

expect(uploadButton).toBeDisabled();
});

it('returns error for invalid file', async () => {
readFileToString.mockReturnValue('bad-content');
await renderCellLevelUploadModal();

const inputEl = await screen.getByTestId('drop-input');
const file = new File(['content'], 'file.tsv', { type: 'text/plain' });

await dropFilesIntoDropzone(inputEl, [file]);
const uploadButton = screen.getByRole('button', {
name: /Upload/,
});

expect(uploadButton).toBeDisabled();
expect(handleError).toHaveBeenCalled();
});

it('needs to have the barcodes column', async () => {
const onUpload = jest.fn();
readFileToString.mockReturnValue('sample\tkey1\tval1');
await renderCellLevelUploadModal({ onUpload });

const inputEl = await screen.getByTestId('drop-input');
const file = new File(['content'], 'file.tsv', { type: 'text/plain' });

await dropFilesIntoDropzone(inputEl, [file]);

expect(handleError).toBeCalledWith('error', 'The .tsv file needs to contain the column "barcodes"');
});
});
36 changes: 24 additions & 12 deletions src/__test__/components/data-management/ProjectDetails.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import thunk from 'redux-thunk';
import '@testing-library/jest-dom';
import configureStore from 'redux-mock-store';
import { act } from 'react-dom/test-utils';
import { fireEvent } from '@testing-library/dom';

import userEvent from '@testing-library/user-event';
import { screen, render, waitFor } from '@testing-library/react';
import {
screen, render, waitFor, fireEvent,
} from '@testing-library/react';

import * as createMetadataTrack from 'redux/actions/experiments/createMetadataTrack';
import * as updateValueInMetadataTrack from 'redux/actions/experiments/updateValueInMetadataTrack';
Expand Down Expand Up @@ -222,9 +224,19 @@ describe('ProjectDetails', () => {
await act(async () => {
userEvent.click(menu);
});
const propertyDropdown = screen.getByText('Sample level');

const options = await screen.getAllByRole('menuitem');
return options;
await act(async () => {
fireEvent.mouseOver(propertyDropdown);
});
await waitFor(() => screen.getByText('Create track'));

const menuItems = {
createTrack: screen.getByText('Create track'),
uploadFile: screen.getByText('Upload file'),
cellLevel: screen.getByText('Cell level'),
};
return menuItems;
};

it('Has a title, project ID and description', () => {
Expand Down Expand Up @@ -317,9 +329,9 @@ describe('ProjectDetails', () => {
</Provider>,
);

const options = await getMenuItems();
const menuOptions = await getMenuItems();

fireEvent.click(options[0]);
fireEvent.click(menuOptions.createTrack);

const input = screen.getByDisplayValue('Track 1');
fireEvent.change(input, { target: { value: 'myBrandNewMetadata' } });
Expand All @@ -337,9 +349,9 @@ describe('ProjectDetails', () => {
</Provider>,
);

const options = await getMenuItems();
const menuOptions = await getMenuItems();

fireEvent.click(options[0]);
fireEvent.click(menuOptions.createTrack);

const input = screen.getByDisplayValue('Track 1');
fireEvent.change(input, { target: { value: 'myBrandNewMetadata' } });
Expand All @@ -358,9 +370,9 @@ describe('ProjectDetails', () => {
);
});

const options = await getMenuItems();
const menuOptions = await getMenuItems();

fireEvent.click(options[0]);
fireEvent.click(menuOptions.createTrack);
const input = screen.getByDisplayValue('Track 1');
fireEvent.change(input, { target: { value: ' myBrandNewMetadata ' } });
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
Expand All @@ -385,9 +397,9 @@ describe('ProjectDetails', () => {
});

// Add track column
const options = await getMenuItems();
const menuOptions = await getMenuItems();

fireEvent.click(options[0]);
fireEvent.click(menuOptions.createTrack);
fireEvent.keyDown(screen.getByDisplayValue('Track 1'), { key: 'Enter', code: 'Enter' });

// Change track value for sample
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Object {
exports[`experimentsReducer Inserts a new experiment correctly 1`] = `
Object {
"experiment-1": Object {
"cellLevelMetadata": Object {},
"createdAt": "2021-01-01",
"description": "this is a test description",
"id": "experiment-1",
Expand Down
103 changes: 82 additions & 21 deletions src/components/data-management/AddMetadataButton.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
/* eslint-disable no-param-reassign */
import React, { useState } from 'react';
import {
Menu, Dropdown, Button,
Menu, Dropdown, Button, Tooltip,
} from 'antd';
import PropTypes from 'prop-types';

import { useSelector, useDispatch } from 'react-redux';
import UploadStatus from 'utils/upload/UploadStatus';

import uploadMetadataFile from 'redux/actions/experiments/uploadMetadataFile';
import { sampleTech } from 'utils/constants';
import {
createCellLevelMetadata,
updateCellLevelMetadataFileUpload,
} from 'redux/actions/experiments';
import { prepareAndUploadFileToS3 } from 'utils/upload/processUpload';
import pushNotificationMessage from 'utils/pushNotificationMessage';
import loadAndCompressIfNecessary from 'utils/upload/loadAndCompressIfNecessary';
import MetadataUploadModal from './MetadataUploadModal';
import CellLevelUploadModal from './CellLevelUploadModal';

const AddMetadataButton = ({ samplesTableRef }) => {
const dispatch = useDispatch();
Expand All @@ -17,32 +27,76 @@ const AddMetadataButton = ({ samplesTableRef }) => {
const isSubsetted = activeExperiment?.isSubsetted;
const samples = useSelector((state) => state.samples);
const selectedTech = samples[activeExperiment?.sampleIds[0]]?.type;
const [uploadModalVisible, setUploadModalVisible] = useState(false);

const uploadFiles = (file) => {
const [trackUploadModalVisible, setTrackUploadModalVisible] = useState(false);
const [cellLevelUploadVisible, setCellLevelUploadVisible] = useState(false);

const [cellLevelUploading, setCellLevelUploading] = useState(false);
const onUploadMetadataTrack = (file) => {
dispatch(uploadMetadataFile(activeExperimentId, file));
setUploadModalVisible(false);
setTrackUploadModalVisible(false);
};

const onUploadCellLevelMetadata = async (file) => {
const getSignedURLsParams = {
name: file.name,
size: file.size,
};

setCellLevelUploading(true);
const onUpdateUploadStatus = (status, percentProgress = 0) => {
dispatch(updateCellLevelMetadataFileUpload(activeExperimentId, status, percentProgress));
};
const uploadUrlParams = await dispatch(
createCellLevelMetadata(activeExperimentId, getSignedURLsParams),
);
file.fileObject = await loadAndCompressIfNecessary(
file, () => onUpdateUploadStatus(UploadStatus.COMPRESSING),
);

try {
await prepareAndUploadFileToS3(file, uploadUrlParams, 'cellLevel', onUpdateUploadStatus);
} catch (e) {
pushNotificationMessage('error', 'Something went wrong while uploading your metadata file.');
console.log(e);
}
setCellLevelUploading(false);
setCellLevelUploadVisible(false);
};

return (
<>
<Dropdown
overlay={() => (
<Menu>
<Menu.Item
key='add-metadata-column'
onClick={() => samplesTableRef.current.createMetadataColumn()}
>
Create track
</Menu.Item>
<Menu.Item
key='upload-metadata-file'
onClick={() => {
setUploadModalVisible(true);
}}
>
Upload file
</Menu.Item>
<Menu.SubMenu title='Sample level' key='sample-level'>
<Menu.Item
key='add-metadata-column'
data-testid='create-track-option'
onClick={() => samplesTableRef.current.createMetadataColumn()}
>
Create track
</Menu.Item>
<Menu.Item
key='upload-metadata-file'
onClick={() => {
setTrackUploadModalVisible(true);
}}
>
Upload file
</Menu.Item>
</Menu.SubMenu>
<Tooltip title='Feature coming soon!'>
<div>
<Menu.Item
key='cell-level'
onClick={() => setCellLevelUploadVisible(true)}
disabled
>
Cell level
</Menu.Item>
</div>
</Tooltip>
</Menu>
)}
trigger={['click']}
Expand All @@ -53,10 +107,17 @@ const AddMetadataButton = ({ samplesTableRef }) => {
Add metadata
</Button>
</Dropdown>
{uploadModalVisible && (
{trackUploadModalVisible && (
<MetadataUploadModal
onUpload={uploadFiles}
onCancel={() => setUploadModalVisible(false)}
onUpload={onUploadMetadataTrack}
onCancel={() => setTrackUploadModalVisible(false)}
/>
)}
{cellLevelUploadVisible && (
<CellLevelUploadModal
uploading={cellLevelUploading}
onUpload={onUploadCellLevelMetadata}
onCancel={() => setCellLevelUploadVisible(false)}
/>
)}
</>
Expand Down
Loading

0 comments on commit 7c39c16

Please sign in to comment.