diff --git a/src/__test__/components/data-management/SamplesTableCells.test.jsx b/src/__test__/components/data-management/SamplesTableCells.test.jsx index b0df1fc6e0..392a9602d0 100644 --- a/src/__test__/components/data-management/SamplesTableCells.test.jsx +++ b/src/__test__/components/data-management/SamplesTableCells.test.jsx @@ -17,6 +17,7 @@ import mockAPI, { generateDefaultMockAPIResponses, statusResponse } from '__test import { loadSamples, updateSampleFileUpload } from 'redux/actions/samples'; import fake from '__test__/test-utils/constants'; +import sampleFileType from 'utils/sampleFileType'; jest.mock('swr', () => () => ({ data: [ @@ -45,7 +46,7 @@ const sampleFileId = `${fake.SAMPLE_FILE_ID}`; enableFetchMocks(); describe('UploadCell', () => { - const fileCategory = 'features.tsv.gz'; + const fileCategory = sampleFileType.FEATURES_10_X; let storeState = null; diff --git a/src/__test__/components/data-management/UploadDetailsModal.test.jsx b/src/__test__/components/data-management/UploadDetailsModal.test.jsx index f8cb267705..667e33afab 100644 --- a/src/__test__/components/data-management/UploadDetailsModal.test.jsx +++ b/src/__test__/components/data-management/UploadDetailsModal.test.jsx @@ -5,7 +5,7 @@ import { import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import UploadDetailsModal from 'components/data-management/UploadDetailsModal'; -import UploadStatus, { messageForStatus } from 'utils/upload/UploadStatus'; +import UploadStatus from 'utils/upload/UploadStatus'; const mockOnDelete = jest.fn(); const mockOnCancel = jest.fn(); @@ -14,8 +14,7 @@ const mockOnRetry = jest.fn(); const defaultProps = { visible: true, - file: { - name: 'example.txt', + data: { size: 1024, lastModified: new Date().toISOString(), upload: { @@ -41,9 +40,9 @@ describe('UploadDetailsModal', () => { it('displays the modal with the correct title when uploaded', () => { renderUploadDetailsModal({ - file: { - ...defaultProps.file, - upload: { ...defaultProps.file.upload, status: UploadStatus.UPLOADED }, + data: { + ...defaultProps.data, + upload: { ...defaultProps.data.upload, status: UploadStatus.UPLOADED }, }, }); @@ -52,8 +51,8 @@ describe('UploadDetailsModal', () => { it('displays the modal with the correct title when file not found', () => { renderUploadDetailsModal({ - file: { - ...defaultProps.file, + data: { + ...defaultProps.data, upload: { status: UploadStatus.FILE_NOT_FOUND }, }, }); @@ -62,9 +61,9 @@ describe('UploadDetailsModal', () => { it('calls onRetry when the retry button is clicked', async () => { renderUploadDetailsModal({ - file: { - ...defaultProps.file, - upload: { ...defaultProps.file.upload, status: UploadStatus.ERROR }, + data: { + ...defaultProps.data, + upload: { ...defaultProps.data.upload, status: UploadStatus.ERROR }, fileObject: [1, 2, 3, 4, 5, 6], }, }); @@ -79,9 +78,9 @@ describe('UploadDetailsModal', () => { it('calls onDownload when the download button is clicked', () => { renderUploadDetailsModal({ - file: { - ...defaultProps.file, - upload: { ...defaultProps.file.upload, status: UploadStatus.UPLOADED }, + data: { + ...defaultProps.data, + upload: { ...defaultProps.data.upload, status: UploadStatus.UPLOADED }, }, }); @@ -94,9 +93,9 @@ describe('UploadDetailsModal', () => { it('renders error message when there is an upload error', () => { const status = UploadStatus.UPLOAD_ERROR; renderUploadDetailsModal({ - file: { - ...defaultProps.file, - upload: { ...defaultProps.file.upload, status }, + data: { + ...defaultProps.data, + upload: { ...defaultProps.data.upload, status }, }, }); @@ -106,9 +105,9 @@ describe('UploadDetailsModal', () => { it('Deleting the file calls onDelete', () => { const status = UploadStatus.UPLOADED; renderUploadDetailsModal({ - file: { - ...defaultProps.file, - upload: { ...defaultProps.file.upload, status }, + data: { + ...defaultProps.data, + upload: { ...defaultProps.data.upload, status }, }, }); diff --git a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap index 149ad37183..cb702417eb 100644 --- a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap +++ b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap @@ -328,35 +328,24 @@ exports[`switch experiment Then updates the experiment info 1`] = ` "test9188-d682-test-mock-cb6d644cmock-0": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", @@ -372,35 +361,24 @@ exports[`switch experiment Then updates the experiment info 1`] = ` "test9188-d682-test-mock-cb6d644cmock-1": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", @@ -416,35 +394,24 @@ exports[`switch experiment Then updates the experiment info 1`] = ` "test9188-d682-test-mock-cb6d644cmock-2": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", @@ -792,35 +759,24 @@ exports[`switch experiment switches the experiment to its initial values 1`] = "test9188-d682-test-mock-cb6d644cmock-0": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", @@ -836,35 +792,24 @@ exports[`switch experiment switches the experiment to its initial values 1`] = "test9188-d682-test-mock-cb6d644cmock-1": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", @@ -880,35 +825,24 @@ exports[`switch experiment switches the experiment to its initial values 1`] = "test9188-d682-test-mock-cb6d644cmock-2": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "testae48e318dab9a1bd0bexperiment-0", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": 100, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": 1000, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", diff --git a/src/__test__/redux/actions/samples/__snapshots__/loadSamples.test.js.snap b/src/__test__/redux/actions/samples/__snapshots__/loadSamples.test.js.snap index a62ad8d8d4..b8c76b0951 100644 --- a/src/__test__/redux/actions/samples/__snapshots__/loadSamples.test.js.snap +++ b/src/__test__/redux/actions/samples/__snapshots__/loadSamples.test.js.snap @@ -15,35 +15,24 @@ exports[`loadSample action Works correctly 1`] = ` "e03ef6ea-5014-4e57-aecd-59964ac9172c": { "createdDate": "2021-12-07 17:36:27.773+00", "experimentId": "1234", - "fileNames": [ - "matrix.mtx.gz", - "barcodes.tsv.gz", - "features.tsv.gz", - ], "files": { - "barcodes.tsv.gz": { - "name": "barcodes.tsv.gz", + "barcodes10x": { "size": undefined, "upload": { "status": "uploaded", }, - "valid": true, }, - "features.tsv.gz": { - "name": "features.tsv.gz", + "features10x": { "size": undefined, "upload": { "status": "uploaded", }, - "valid": true, }, - "matrix.mtx.gz": { - "name": "matrix.mtx.gz", + "matrix10x": { "size": undefined, "upload": { "status": "uploaded", }, - "valid": true, }, }, "lastModified": "2021-12-07 17:38:42.036+00", diff --git a/src/__test__/redux/reducers/__snapshots__/samplesReducer.test.js.snap b/src/__test__/redux/reducers/__snapshots__/samplesReducer.test.js.snap index a38d819fda..d5517b9e74 100644 --- a/src/__test__/redux/reducers/__snapshots__/samplesReducer.test.js.snap +++ b/src/__test__/redux/reducers/__snapshots__/samplesReducer.test.js.snap @@ -7,7 +7,6 @@ exports[`samplesReducer Adds a new sample correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -27,7 +26,6 @@ exports[`samplesReducer Adds a new sample correctly 1`] = ` "createdDate": "2021-01-02T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-02T14:48:00.000Z", "metadata": {}, @@ -46,7 +44,6 @@ exports[`samplesReducer Adds validating to an experiment 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -70,7 +67,6 @@ exports[`samplesReducer Adds validating to an experiment 1`] = ` "createdDate": "2021-01-02T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-02T14:48:00.000Z", "metadata": {}, @@ -91,7 +87,6 @@ exports[`samplesReducer Delete samples correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -121,7 +116,6 @@ exports[`samplesReducer Deletes sample metadata correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -179,7 +173,6 @@ exports[`samplesReducer Inserts a new sample correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -207,7 +200,6 @@ exports[`samplesReducer Inserts sample metadata correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": { @@ -234,7 +226,6 @@ exports[`samplesReducer Loads samples correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -258,7 +249,6 @@ exports[`samplesReducer Loads samples correctly 1`] = ` "createdDate": "2021-01-02T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-02T14:48:00.000Z", "metadata": {}, @@ -277,7 +267,6 @@ exports[`samplesReducer Removes validating from an experiment 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -298,7 +287,6 @@ exports[`samplesReducer Removes validating from an experiment 1`] = ` "createdDate": "2021-01-02T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-02T14:48:00.000Z", "metadata": {}, @@ -319,7 +307,6 @@ exports[`samplesReducer Sets up saved state correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -343,7 +330,6 @@ exports[`samplesReducer Sets up saving state correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -367,7 +353,6 @@ exports[`samplesReducer Stores error state correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -391,7 +376,6 @@ exports[`samplesReducer Updates a sample correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -416,7 +400,6 @@ exports[`samplesReducer Updates options correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-01T14:48:00.000Z", "metadata": {}, @@ -438,7 +421,6 @@ exports[`samplesReducer Updates options correctly 1`] = ` "createdDate": "2021-01-02T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [], "files": {}, "lastModified": "2021-01-02T14:48:00.000Z", "metadata": {}, @@ -459,21 +441,13 @@ exports[`samplesReducer Updates sample files correctly 1`] = ` "createdDate": "2021-01-01T14:48:00.000Z", "error": false, "experimentId": null, - "fileNames": [ - "features.tsv", - ], "files": { - "features.tsv": { + "features10x": { "error": false, - "lastModified": "newLastModified", - "mime": "", - "name": "features.tsv", "objectKey": "", - "path": "", "size": 0, "success": false, "upload": { - "amplifyPromise": null, "status": null, }, }, diff --git a/src/__test__/redux/reducers/samplesReducer.test.js b/src/__test__/redux/reducers/samplesReducer.test.js index 780b919e6f..cdeccf286c 100644 --- a/src/__test__/redux/reducers/samplesReducer.test.js +++ b/src/__test__/redux/reducers/samplesReducer.test.js @@ -16,11 +16,11 @@ import { SAMPLES_VALIDATING_UPDATED, } from 'redux/actionTypes/samples'; import { EXPERIMENTS_METADATA_RENAME } from 'redux/actionTypes/experiments'; +import sampleFileType from 'utils/sampleFileType'; describe('samplesReducer', () => { const mockUuid1 = 'asd123'; const mockUuid2 = 'qwe234'; - const fileName = 'features.tsv'; const sample1 = { ...sampleTemplate, @@ -55,7 +55,6 @@ describe('samplesReducer', () => { const mockFile = { ...sampleFileTemplate, - name: fileName, }; it('Reduces identical state on unknown action', () => expect( @@ -108,17 +107,13 @@ describe('samplesReducer', () => { type: SAMPLES_FILE_UPDATE, payload: { sampleUuid: mockUuid1, - fileName, + sampleFileType: sampleFileType.FEATURES_10_X, fileDiff: mockFile, lastModified: 'newLastModified', }, }); - expect(newState[sample1.uuid].fileNames).toEqual([fileName]); - expect(newState[sample1.uuid].files[fileName]).toEqual({ - ...mockFile, - lastModified: 'newLastModified', - }); + expect(newState[sample1.uuid].files[sampleFileType.FEATURES_10_X]).toEqual(mockFile); expect(newState).toMatchSnapshot(); }); @@ -404,7 +399,7 @@ describe('samplesReducer', () => { type: SAMPLES_FILE_UPDATE, payload: { sampleUuid: mockUuid1, - fileName, + sampleFileType: sampleFileType.FEATURES_10_X, fileDiff: mockFile, lastModified: 'newLastModified', }, diff --git a/src/__test__/test-utils/mockData/generateMockSamples.js b/src/__test__/test-utils/mockData/generateMockSamples.js index 458868d210..39e3024ca7 100644 --- a/src/__test__/test-utils/mockData/generateMockSamples.js +++ b/src/__test__/test-utils/mockData/generateMockSamples.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import fake from '__test__/test-utils/constants'; +import sampleFileType from 'utils/sampleFileType'; const mockSampleTemplate = (experimentId, sampleId, idx) => ({ id: sampleId, @@ -13,19 +14,19 @@ const mockSampleTemplate = (experimentId, sampleId, idx) => ({ files: { matrix10X: { uploadStatus: 'uploaded', - sampleFileType: 'matrix10x', + sampleFileType: sampleFileType.MATRIX_10_X, size: 1000, s3Path: 'testcfd8122f-25af-4f1a-a306-3268d44ed401', }, barcodes10X: { uploadStatus: 'uploaded', - sampleFileType: 'barcodes10x', + sampleFileType: sampleFileType.BARCODES_10_X, size: 100, s3Path: 'testcfd8122f-25af-4f1a-a306-3268d44ed402', }, features10X: { uploadStatus: 'uploaded', - sampleFileType: 'features10x', + sampleFileType: sampleFileType.FEATURES_10_X, size: 100, s3Path: 'testcfd8122f-25af-4f1a-a306-3268d44ed403', }, diff --git a/src/__test__/utils/data-management/downloadSingleFile.test.js b/src/__test__/utils/data-management/downloadSingleFile.test.js index 1c9d36d265..f3abcb513d 100644 --- a/src/__test__/utils/data-management/downloadSingleFile.test.js +++ b/src/__test__/utils/data-management/downloadSingleFile.test.js @@ -5,6 +5,7 @@ import downloadFromUrl from 'utils/downloadFromUrl'; import { sampleTech } from 'utils/constants'; import fake from '__test__/test-utils/constants'; +import sampleFileType from 'utils/sampleFileType'; jest.mock('utils/downloadFromUrl'); @@ -23,9 +24,7 @@ describe('downloadFromUrl', () => { fetchMock.mockResponse(JSON.stringify(mockSignedUrl)); - const fileName = 'features.tsv.gz'; - - await downloadSingleFile(fake.EXPERIMENT_ID, fake.SAMPLE_ID, fileName, sampleTech['10X']); + await downloadSingleFile(fake.EXPERIMENT_ID, fake.SAMPLE_ID, sampleFileType.FEATURES_10_X, sampleTech['10X']); expect(downloadFromUrl).toHaveBeenCalledWith(mockSignedUrl); expect(fetchMock.mock.calls).toMatchSnapshot(); diff --git a/src/__test__/utils/upload/process10XUpload.test.js b/src/__test__/utils/upload/process10XUpload.test.js index 7fa6b982b7..9698d3b98a 100644 --- a/src/__test__/utils/upload/process10XUpload.test.js +++ b/src/__test__/utils/upload/process10XUpload.test.js @@ -25,28 +25,28 @@ import loadAndCompressIfNecessary from 'utils/upload/loadAndCompressIfNecessary' enableFetchMocks(); const getValidFiles = (cellrangerVersion, compressed = true) => { - const filename = cellrangerVersion === 'v2' ? 'genes.tsv.gz' : 'features.tsv.gz'; + const featuresFilename = cellrangerVersion === 'v2' ? 'genes.tsv.gz' : 'features.tsv.gz'; let fileList = [ { - name: `WT13/${filename}`, - fileObject: mockFile(filename, '/'), + name: `${featuresFilename}`, + fileObject: mockFile(featuresFilename, 'WT13'), upload: { status: UploadStatus.UPLOADING }, errors: '', compressed, valid: true, }, { - name: 'WT13/barcodes.tsv.gz', - fileObject: mockFile('barcodes.tsv.gz', '/'), + name: 'barcodes.tsv.gz', + fileObject: mockFile('barcodes.tsv.gz', 'WT13'), upload: { status: UploadStatus.UPLOADING }, errors: '', compressed, valid: true, }, { - name: 'WT13/matrix.mtx.gz', - fileObject: mockFile('matrix.mtx.gz', '/'), + name: 'matrix.mtx.gz', + fileObject: mockFile('matrix.mtx.gz', 'WT13'), upload: { status: UploadStatus.UPLOADING }, errors: '', compressed, diff --git a/src/__test__/utils/upload/validate10x.test.js b/src/__test__/utils/upload/validate10x.test.js index 722e0c4325..5a14773248 100644 --- a/src/__test__/utils/upload/validate10x.test.js +++ b/src/__test__/utils/upload/validate10x.test.js @@ -2,6 +2,7 @@ import validate10x from 'utils/upload/validate10x'; import initialState, { sampleFileTemplate, sampleTemplate } from 'redux/reducers/samples/initialState'; import * as fs from 'fs'; +import sampleFileType from 'utils/sampleFileType'; const _ = require('lodash'); @@ -65,34 +66,23 @@ const mockZippedSample = { ...sampleTemplate, ...initialState, name: 'mockZippedSample', - fileNames: [ - 'features.tsv.gz', - 'barcodes.tsv.gz', - 'matrix.mtx.gz', - ], files: { - 'features.tsv.gz': { + [sampleFileType.FEATURES_10_X]: { ...sampleFileTemplate, - name: 'features.tsv.gz', fileObject: mockZippedFileObjects['features.tsv.gz'], size: mockZippedFileObjects['features.tsv.gz'].size, - path: '/transposed/features.tsv.gz', compressed: true, }, - 'barcodes.tsv.gz': { + [sampleFileType.BARCODES_10_X]: { ...sampleFileTemplate, - name: 'barcodes.tsv.gz', fileObject: mockZippedFileObjects['barcodes.tsv.gz'], size: mockZippedFileObjects['barcodes.tsv.gz'].size, - path: '/transposed/barcodes.tsv.gz', compressed: true, }, - 'matrix.mtx.gz': { + [sampleFileType.MATRIX_10_X]: { ...sampleFileTemplate, - name: 'matrix.mtx.gz', fileObject: mockZippedFileObjects['matrix.mtx.gz'], size: mockZippedFileObjects['matrix.mtx.gz'].size, - path: '/transposed/matrix.mtx.gz', compressed: true, }, }, @@ -102,34 +92,23 @@ const mockUnzippedSample = { ...sampleTemplate, ...initialState, name: 'mockUnzippedSample', - fileNames: [ - 'features.tsv', - 'barcodes.tsv', - 'matrix.mtx', - ], files: { - 'features.tsv': { + [sampleFileType.FEATURES_10_X]: { ...sampleFileTemplate, - name: 'features.tsv', fileObject: mockUnzippedFileObjects['features.tsv'], size: mockUnzippedFileObjects['features.tsv'].size, - path: '/transposed/features.tsv', compressed: false, }, - 'barcodes.tsv': { + [sampleFileType.BARCODES_10_X]: { ...sampleFileTemplate, - name: 'barcodes.tsv', fileObject: mockUnzippedFileObjects['barcodes.tsv'], size: mockUnzippedFileObjects['barcodes.tsv'].size, - path: '/transposed/barcodes.tsv', compressed: false, }, - 'matrix.mtx': { + [sampleFileType.MATRIX_10_X]: { ...sampleFileTemplate, - name: 'matrix.mtx', fileObject: mockUnzippedFileObjects['matrix.mtx'], size: mockUnzippedFileObjects['matrix.mtx'].size, - path: '/transposed/matrix.mtx', compressed: false, }, }, @@ -145,75 +124,61 @@ describe('validate10x', () => { }); it('Throws an error for missing barcodes file', async () => { - const missingFile = 'barcodes.tsv.gz'; - const missingBarcodesFile = _.cloneDeep(mockZippedSample); - missingBarcodesFile.fileNames = missingBarcodesFile.fileNames.filter( - (name) => name !== missingFile, - ); - delete missingBarcodesFile.files[missingFile]; + + delete missingBarcodesFile.files[sampleFileType.BARCODES_10_X]; await expect(validate10x(missingBarcodesFile)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error for missing features file', async () => { - const missingFile = 'features.tsv.gz'; - const missingFeaturesFile = _.cloneDeep(mockZippedSample); - missingFeaturesFile.fileNames = missingFeaturesFile.fileNames.filter( - (name) => name !== missingFile, - ); - delete missingFeaturesFile.files[missingFile]; + delete missingFeaturesFile.files[sampleFileType.FEATURES_10_X]; await expect(validate10x(missingFeaturesFile)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error for missing matrix file', async () => { - const missingFile = 'martix.mtx.gz'; - const missingMatrixFile = _.cloneDeep(mockZippedSample); - missingMatrixFile.fileNames = missingMatrixFile.fileNames.filter( - (name) => name !== missingFile, - ); - delete missingMatrixFile.files[missingFile]; + delete missingMatrixFile.files[sampleFileType.MATRIX_10_X]; }); it('Throws an error matrix with array format', async () => { const mockMatrixArrayFormat = _.cloneDeep(mockZippedSample); - mockMatrixArrayFormat.files['matrix.mtx.gz'].fileObject = mockZippedFileObjects['matrix_array_format.mtx.gz']; - mockMatrixArrayFormat.files['matrix.mtx.gz'].size = mockZippedFileObjects['matrix_array_format.mtx.gz'].size; + mockMatrixArrayFormat.files[sampleFileType.MATRIX_10_X].fileObject = mockZippedFileObjects['matrix_array_format.mtx.gz']; + mockMatrixArrayFormat.files[sampleFileType.MATRIX_10_X].size = mockZippedFileObjects['matrix_array_format.mtx.gz'].size; await expect(validate10x(mockMatrixArrayFormat)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error invalid matrix format', async () => { const mockMatrixNonExistentFormat = _.cloneDeep(mockZippedSample); - mockMatrixNonExistentFormat.files['matrix.mtx.gz'].fileObject = mockZippedFileObjects['matrix_invalid_format.mtx.gz']; - mockMatrixNonExistentFormat.files['matrix.mtx.gz'].size = mockZippedFileObjects['matrix_invalid_format.mtx.gz'].size; + mockMatrixNonExistentFormat.files[sampleFileType.MATRIX_10_X].fileObject = mockZippedFileObjects['matrix_invalid_format.mtx.gz']; + mockMatrixNonExistentFormat.files[sampleFileType.MATRIX_10_X].size = mockZippedFileObjects['matrix_invalid_format.mtx.gz'].size; await expect(validate10x(mockMatrixNonExistentFormat)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error for invalid barcodes file', async () => { const mockInvalidBarcodesFile = _.cloneDeep(mockZippedSample); - mockInvalidBarcodesFile.files['barcodes.tsv.gz'].fileObject = mockZippedFileObjects['invalid_barcodes.tsv.gz']; - mockInvalidBarcodesFile.files['barcodes.tsv.gz'].size = mockZippedFileObjects['invalid_barcodes.tsv.gz'].size; + mockInvalidBarcodesFile.files[sampleFileType.BARCODES_10_X].fileObject = mockZippedFileObjects['invalid_barcodes.tsv.gz']; + mockInvalidBarcodesFile.files[sampleFileType.BARCODES_10_X].size = mockZippedFileObjects['invalid_barcodes.tsv.gz'].size; await expect(validate10x(mockInvalidBarcodesFile)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error for invalid features file', async () => { const mockInvalidFeaturesFile = _.cloneDeep(mockZippedSample); - mockInvalidFeaturesFile.files['features.tsv.gz'].fileObject = mockZippedFileObjects['invalid_features.tsv.gz']; - mockInvalidFeaturesFile.files['features.tsv.gz'].size = mockZippedFileObjects['invalid_features.tsv.gz'].size; + mockInvalidFeaturesFile.files[sampleFileType.FEATURES_10_X].fileObject = mockZippedFileObjects['invalid_features.tsv.gz']; + mockInvalidFeaturesFile.files[sampleFileType.FEATURES_10_X].size = mockZippedFileObjects['invalid_features.tsv.gz'].size; await expect(validate10x(mockInvalidFeaturesFile)).rejects.toThrowErrorMatchingSnapshot(); }); it('Throws an error for transposed matrix file', async () => { const mockTransposedFile = _.cloneDeep(mockZippedSample); - mockTransposedFile.files['matrix.mtx.gz'].fileObject = mockZippedFileObjects['transposed_matrix.mtx.gz']; - mockTransposedFile.files['matrix.mtx.gz'].size = mockZippedFileObjects['transposed_matrix.mtx.gz'].size; + mockTransposedFile.files[sampleFileType.MATRIX_10_X].fileObject = mockZippedFileObjects['transposed_matrix.mtx.gz']; + mockTransposedFile.files[sampleFileType.MATRIX_10_X].size = mockZippedFileObjects['transposed_matrix.mtx.gz'].size; await expect(validate10x(mockTransposedFile)).rejects.toThrowErrorMatchingSnapshot(); }); diff --git a/src/__test__/utils/upload/validateRhapsody.test.js b/src/__test__/utils/upload/validateRhapsody.test.js index 0baaec0f6d..9660a40f46 100644 --- a/src/__test__/utils/upload/validateRhapsody.test.js +++ b/src/__test__/utils/upload/validateRhapsody.test.js @@ -57,12 +57,10 @@ const mockUnzippedSample = { 'expression_data.st', ], files: { - 'expression_data.st': { + rhapsody: { ...sampleFileTemplate, - name: 'expression_data.st', fileObject: mockUnzippedFileObjects['expression_data.st'], size: mockUnzippedFileObjects['expression_data.st'].size, - path: '/sample1/expression_data.st', compressed: false, }, }, @@ -75,8 +73,8 @@ describe('validateRhapsody', () => { it('Throws an error invalid column format', async () => { const mockInvalidColumn = _.cloneDeep(mockUnzippedSample); - mockInvalidColumn.files['expression_data.st'].fileObject = mockUnzippedFileObjects['expression_data_invalid_column.st']; - mockInvalidColumn.files['expression_data.st'].size = mockUnzippedFileObjects['expression_data_invalid_column.st'].size; + mockInvalidColumn.files.rhapsody.fileObject = mockUnzippedFileObjects['expression_data_invalid_column.st']; + mockInvalidColumn.files.rhapsody.size = mockUnzippedFileObjects['expression_data_invalid_column.st'].size; await expect(validateRhapsody(mockInvalidColumn)).rejects.toThrowErrorMatchingSnapshot(); }); diff --git a/src/components/data-management/FileUploadModal.jsx b/src/components/data-management/FileUploadModal.jsx index 1469d0ee79..cc3180a4a8 100644 --- a/src/components/data-management/FileUploadModal.jsx +++ b/src/components/data-management/FileUploadModal.jsx @@ -22,7 +22,7 @@ import config from 'config'; import { sampleTech } from 'utils/constants'; import techOptions, { techNamesToDisplay } from 'utils/upload/fileUploadSpecifications'; import handleError from 'utils/http/handleError'; -import { fileObjectToFileRecord } from 'utils/upload/processUpload'; +import { fileObjectToFileRecord, getFileSampleAndName } from 'utils/upload/processUpload'; import integrationTestConstants from 'utils/integrationTestConstants'; import endUserMessages from 'utils/endUserMessages'; @@ -67,7 +67,7 @@ const FileUploadModal = (props) => { const [filesList, setFilesList] = useState([]); useEffect(() => { - setCanUpload(filesList.length && filesList.every((file) => file.valid)); + setCanUpload(filesList.length && filesList.every((file) => !file.errors)); }, [filesList]); useEffect(() => { @@ -107,8 +107,8 @@ const FileUploadModal = (props) => { filteredFiles = filteredFiles // Remove all files that aren't in a folder - .filter((file) => { - const inFolder = file.path.includes('/'); + .filter((fileObject) => { + const inFolder = fileObject.path.includes('/'); filesNotInFolder ||= !inFolder; @@ -169,6 +169,8 @@ const FileUploadModal = (props) => { ); + const getFilePathToDisplay = (fileObject) => _.trim(Object.values(getFileSampleAndName(fileObject.path)).join('/'), '/'); + return ( { defaultValue={selectedTech} disabled={currentSelectedTech} onChange={(value) => setSelectedTech(value)} - style={{ width: 180 }} // Fix the width so that the dropdown doesn't change size when the value changes + // Fix the width so that the dropdown doesn't change size when the value changes + style={{ width: 180 }} > { Object.values(sampleTech) @@ -300,7 +303,7 @@ const FileUploadModal = (props) => { style={{ width: '100%' }} > - {file.valid + {!file.errors ? ( <> @@ -314,7 +317,7 @@ const FileUploadModal = (props) => { ellipsis={{ tooltip: file.name }} style={{ width: '200px' }} > - {file.name} + {getFilePathToDisplay(file.fileObject)} { removeFile(file.name); }} /> diff --git a/src/components/data-management/LaunchAnalysisButton.jsx b/src/components/data-management/LaunchAnalysisButton.jsx index 2ad57a81eb..b984bb7d5f 100644 --- a/src/components/data-management/LaunchAnalysisButton.jsx +++ b/src/components/data-management/LaunchAnalysisButton.jsx @@ -7,7 +7,6 @@ import _ from 'lodash'; import { modules, sampleTech } from 'utils/constants'; -import fileUploadSpecifications from 'utils/upload/fileUploadSpecifications'; import UploadStatus from 'utils/upload/UploadStatus'; import integrationTestConstants from 'utils/integrationTestConstants'; @@ -99,29 +98,9 @@ const LaunchAnalysisButton = () => { const metadataKeysAvailable = activeExperiment.metadataKeys.length; - const allSampleFilesUploaded = (sample) => { - // Check if all files for a given tech has been uploaded - const { fileNames } = sample; - if ( - !fileUploadSpecifications[sample.type].requiredFiles.every( - (file) => fileNames.includes(file.key), - ) - ) { return false; } - - let allUploaded = true; - - // eslint-disable-next-line no-restricted-syntax - for (const fileName of fileNames) { - const checkedFile = sample.files[fileName]; - allUploaded = allUploaded - && checkedFile.valid - && checkedFile.upload.status === UploadStatus.UPLOADED; - - if (!allUploaded) break; - } - - return allUploaded; - }; + const allSampleFilesUploaded = (sample) => ( + Object.values(sample.files).every((file) => file.upload.status === UploadStatus.UPLOADED) + ); const allSampleMetadataInserted = (sample) => { if (!metadataKeysAvailable) return true; diff --git a/src/components/data-management/SamplesTable.jsx b/src/components/data-management/SamplesTable.jsx index a6275be0c0..b6775f8da2 100644 --- a/src/components/data-management/SamplesTable.jsx +++ b/src/components/data-management/SamplesTable.jsx @@ -38,6 +38,7 @@ import integrationTestConstants from 'utils/integrationTestConstants'; import useConditionalEffect from 'utils/customHooks/useConditionalEffect'; import fileUploadSpecifications from 'utils/upload/fileUploadSpecifications'; import { sampleTech } from 'utils/constants'; +import { fileTypeToDisplay } from 'utils/sampleFileType'; const { Text } = Typography; @@ -70,51 +71,51 @@ const SamplesTable = forwardRef((props, ref) => { const [samplesLoaded, setSamplesLoaded] = useState(false); - const initialTableColumns = useMemo(() => ([ - { - fixed: 'left', - index: 0, - key: 'sort', - dataIndex: 'sort', - width: 50, - render: () => , - }, - { - className: `${integrationTestConstants.classes.SAMPLE_CELL}`, - index: 1, - key: 'sample', - title: selectedTech === sampleTech.SEURAT ? 'File' : 'Sample', - dataIndex: 'name', - fixed: 'left', - render: (text, record, indx) => ( - - ), - }, - ...fileUploadSpecifications[selectedTech]?.requiredFiles?.map((fileName, indx) => { - const fileNameWithoutExtension = fileName.key.split('.')[0]; + const initialTableColumns = useMemo(() => { + if (_.isNil(selectedTech)) return []; - return ({ + return ([ + { + fixed: 'left', + index: 0, + key: 'sort', + dataIndex: 'sort', + width: 50, + render: () => , + }, + { + className: `${integrationTestConstants.classes.SAMPLE_CELL}`, + index: 1, + key: 'sample', + title: selectedTech === sampleTech.SEURAT ? 'File' : 'Sample', + dataIndex: 'name', + fixed: 'left', + render: (text, record, indx) => ( + + ), + }, + ...fileUploadSpecifications[selectedTech].requiredFiles.map((requiredFile, indx) => ({ index: 2 + indx, - title:
{fileName.displayedName}
, - key: fileNameWithoutExtension, - dataIndex: fileNameWithoutExtension, + title:
{fileTypeToDisplay[requiredFile]}
, + key: requiredFile, + dataIndex: requiredFile, width: 170, onCell: () => ({ style: { margin: '0px', padding: '0px' } }), render: (tableCellData) => tableCellData && ( ), - }); - }) || [], + })), - ]), [selectedTech]); + ]); + }, [selectedTech]); const [tableColumns, setTableColumns] = useState(initialTableColumns); useEffect(() => { - if (activeExperiment?.sampleIds.length > 0 && samplesLoaded) { + if (activeExperiment?.sampleIds.length > 0 && !samplesLoading) { // if there are samples - build the table columns const sanitizedSampleNames = new Set( activeExperiment.sampleIds.map((id) => samples[id]?.name.trim()), @@ -124,9 +125,10 @@ const SamplesTable = forwardRef((props, ref) => { const metadataColumns = activeExperiment.metadataKeys.map( (metadataKey) => createInitializedMetadataColumn(metadataKeyToName(metadataKey)), ) || []; + setTableColumns([...initialTableColumns, ...metadataColumns]); } - }, [samples, activeExperiment?.sampleIds, samplesLoaded]); + }, [samples, activeExperiment?.sampleIds, samplesLoading]); useConditionalEffect(() => { setSamplesLoaded(false); @@ -231,17 +233,14 @@ const SamplesTable = forwardRef((props, ref) => { }; const generateDataForItem = useCallback((sampleUuid) => { - const sampleFileNames = fileUploadSpecifications[selectedTech]?.requiredFiles - .map((fileName) => ([ - fileName.key.split('.')[0], - { sampleUuid }, - ])); + const sampleFileTypes = fileUploadSpecifications[selectedTech]?.requiredFiles + .map((requiredFile) => ([requiredFile, { sampleUuid }])); return { key: sampleUuid, name: samples[sampleUuid]?.name || 'UPLOAD ERROR: Please reupload sample', uuid: sampleUuid, - ...Object.fromEntries(sampleFileNames), + ...Object.fromEntries(sampleFileTypes), }; }, [activeExperiment?.sampleIds, selectedTech, samples]); diff --git a/src/components/data-management/SamplesTableCells.jsx b/src/components/data-management/SamplesTableCells.jsx index 9b998e10f9..7b94492817 100644 --- a/src/components/data-management/SamplesTableCells.jsx +++ b/src/components/data-management/SamplesTableCells.jsx @@ -1,3 +1,4 @@ +import _ from 'lodash'; import React, { useEffect, useState } from 'react'; import { Space, Typography, Progress, Tooltip, Button, @@ -16,9 +17,8 @@ import integrationTestConstants from 'utils/integrationTestConstants'; import UploadStatus, { messageForStatus } from 'utils/upload/UploadStatus'; import styles from 'components/data-management/SamplesTableCells.module.css'; import downloadSampleFile from 'utils/data-management/downloadSampleFile'; -import { createAndUploadSampleFile, fileObjectToFileRecord } from 'utils/upload/processUpload'; -import endUserMessages from 'utils/endUserMessages'; -import handleError from 'utils/http/handleError'; +import { createAndUploadSampleFile } from 'utils/upload/processUpload'; +import { fileTypeToDisplay } from 'utils/sampleFileType'; import EditableField from '../EditableField'; import UploadDetailsModal from './UploadDetailsModal'; @@ -40,7 +40,7 @@ const UploadCell = (props) => { const { activeExperimentId } = useSelector((state) => state.experiments.meta); const [uploadDetailsModalVisible, setUploadDetailsModalVisible] = useState(false); - const [uploadDetailsModalData, setUploadDetailsModalData] = useState(false); + const [uploadDetailsModalData, setUploadDetailsModalData] = useState(null); useEffect(() => { setUploadDetailsModalData(file); @@ -127,25 +127,23 @@ const UploadCell = (props) => { } }; const onDownload = () => { - downloadSampleFile(activeExperimentId, sampleUuid, uploadDetailsModalData.name, selectedTech); + downloadSampleFile( + activeExperimentId, sampleUuid, uploadDetailsModalData.fileCategory, + ); }; - const onUpload = (fileObject, retryUpload = false) => { - if (!uploadDetailsModalData) { - return; - } + const onRetry = () => { + const fileType = uploadDetailsModalData.fileCategory; + // if retrying an upload we dont need to revalidate the file since it was done before - if (retryUpload) { - createAndUploadSampleFile(fileObject, activeExperimentId, sampleUuid, dispatch, selectedTech); - } else { - fileObjectToFileRecord(fileObject, selectedTech).then((newFile) => { - if (newFile.valid) { - createAndUploadSampleFile(newFile, activeExperimentId, sampleUuid, dispatch, selectedTech); - } else { - handleError('error', endUserMessages.ERROR_FILE_CATEGORY); - } - }); - } + createAndUploadSampleFile( + file, + fileType, + activeExperimentId, + sampleUuid, + dispatch, + selectedTech, + ); setUploadDetailsModalVisible(false); }; @@ -157,14 +155,14 @@ const UploadCell = (props) => { {uploadDetailsModalVisible && ( setUploadDetailsModalVisible(false)} onDownload={onDownload} onDelete={() => dispatch(deleteSamples([sampleUuid]))} - onRetry={() => onUpload(uploadDetailsModalData, true)} + onRetry={() => onRetry()} extraFields={{ Sample: sample?.name, - Category: uploadDetailsModalData.fileCategory, + Category: fileTypeToDisplay[uploadDetailsModalData.fileCategory], }} /> )} diff --git a/src/components/data-management/UploadDetailsModal.jsx b/src/components/data-management/UploadDetailsModal.jsx index 5e7d4ba4fa..80fd0c5a96 100644 --- a/src/components/data-management/UploadDetailsModal.jsx +++ b/src/components/data-management/UploadDetailsModal.jsx @@ -12,20 +12,19 @@ dayjs.extend(utc); const UploadDetailsModal = (props) => { const { - onCancel, file, extraFields, onDownload, onRetry, onDelete, + onCancel, data, extraFields, onDownload, onRetry, onDelete, } = props; const { - name, upload, size, lastModified, fileObject = undefined, - } = file ?? {}; + upload, size, lastModified, fileObject = undefined, + } = data; - const { progress, status } = upload ?? false; + const { progress, status } = upload; const isSuccessModal = status === UploadStatus.UPLOADED; const isNotUploadedModal = status === UploadStatus.FILE_NOT_FOUND; const isUploading = status === UploadStatus.UPLOADING; - // title={!isNotUploadedModal ? (isSuccessModal ? 'Upload successful' : 'Upload error') : 'File not found'} const modalTitle = messageForStatus(status); function bytesToSize(bytes) { @@ -119,8 +118,6 @@ const UploadDetailsModal = (props) => { )} {renderFields(extraFields)} - {!isNotUploadedModal && renderFields({ Filename: name })} - { isSuccessModal || isUploading ? renderFields({ 'File size': bytesToSize(size), 'Upload date': fromISODateToFormatted(lastModified) }) : renderFields({ Error: messageForStatus(status) }) @@ -135,7 +132,7 @@ const UploadDetailsModal = (props) => { UploadDetailsModal.propTypes = { onCancel: PropTypes.func.isRequired, - file: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, extraFields: PropTypes.object, onDownload: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, diff --git a/src/components/data-management/metadata/CellLevelUploadModal.jsx b/src/components/data-management/metadata/CellLevelUploadModal.jsx index 0f586822f1..939911126b 100644 --- a/src/components/data-management/metadata/CellLevelUploadModal.jsx +++ b/src/components/data-management/metadata/CellLevelUploadModal.jsx @@ -272,7 +272,7 @@ const CellLevelUploadModal = (props) => { onDownload={downloadData} onDelete={() => dispatch(deleteCellLevelMetadata(activeExperimentId))} onRetry={() => onUploadFile(file)} - file={fileInfoObject} + data={fileInfoObject} /> ); } diff --git a/src/pages/experiments/[experimentId]/plots-and-tables/dot-plot/index.jsx b/src/pages/experiments/[experimentId]/plots-and-tables/dot-plot/index.jsx index a0bf1f7973..804f994df6 100644 --- a/src/pages/experiments/[experimentId]/plots-and-tables/dot-plot/index.jsx +++ b/src/pages/experiments/[experimentId]/plots-and-tables/dot-plot/index.jsx @@ -22,7 +22,7 @@ import Header from 'components/Header'; import PlotContainer from 'components/plots/PlotContainer'; import Loader from 'components/Loader'; import ExportAsCSV from 'components/plots/ExportAsCSV'; -import fileNames from 'utils/fileNames'; +import plotCsvFilename from 'utils/plotCsvFilename'; import { updatePlotConfig, loadPlotConfig, @@ -101,7 +101,7 @@ const DotPlotPage = (props) => { const highestGenesLoadedRef = useRef(false); const experimentName = useSelector((state) => state.experimentSettings.info.experimentName); - const csvFileName = fileNames(experimentName, 'DOT_PLOT', [config?.selectedCellSet, config?.selectedPoints]); + const csvFileName = plotCsvFilename(experimentName, 'DOT_PLOT', [config?.selectedCellSet, config?.selectedPoints]); useEffect(() => { if (!config) dispatch(loadPlotConfig(experimentId, plotUuid, plotType)); diff --git a/src/pages/experiments/[experimentId]/plots-and-tables/frequency/index.jsx b/src/pages/experiments/[experimentId]/plots-and-tables/frequency/index.jsx index 57a903d9f0..563646f81f 100644 --- a/src/pages/experiments/[experimentId]/plots-and-tables/frequency/index.jsx +++ b/src/pages/experiments/[experimentId]/plots-and-tables/frequency/index.jsx @@ -25,7 +25,7 @@ import SelectCellSets from 'components/plots/styling/frequency/SelectCellSets'; import { updatePlotConfig, loadPlotConfig } from 'redux/actions/componentConfig'; import loadCellSets from 'redux/actions/cellSets/loadCellSets'; -import plotCsvFilename from 'utils/fileNames'; +import plotCsvFilename from 'utils/plotCsvFilename'; import { plotNames } from 'utils/constants'; import PlotContainer from 'components/plots/PlotContainer'; @@ -192,7 +192,7 @@ const FrequencyPlotPage = ({ experimentId }) => { return ( - { config?.legend?.showAlert && } + {config?.legend?.showAlert && }
async (dispatch) => { const updatedAt = dayjs().toISOString(); @@ -23,17 +23,25 @@ const createSampleFile = ( const url = `/v2/experiments/${experimentId}/samples/${sampleId}/sampleFiles/${type}`; const body = { sampleFileId, - size: fileForApiV1.size, + size: file.size, }; + // Leaving out path, errors + // They are used during the upload process, not redux + // TODO we should check if they can be separated somehow between + // The ones that are relevant for the api vs + // the ones that are only necessary for retry (fileObject, compressed) + // Perhaps into an uploadRetryParams object or something + const fileForRedux = _.pick(file, ['size', 'upload', 'fileObject', 'compressed']); + dispatch({ type: SAMPLES_FILE_UPDATE, payload: { sampleUuid: sampleId, + sampleFileType: type, lastModified: updatedAt, - fileName: fileNameForApiV1[type], fileDiff: { - ...fileForApiV1, + ...fileForRedux, upload: { status: UploadStatus.UPLOADING, progress: 0, abortController, }, diff --git a/src/redux/actions/samples/createSamples.js b/src/redux/actions/samples/createSamples.js index ccbad5ab71..d34ccbd81a 100644 --- a/src/redux/actions/samples/createSamples.js +++ b/src/redux/actions/samples/createSamples.js @@ -14,9 +14,6 @@ import { defaultSampleOptions, sampleTemplate } from 'redux/reducers/samples/ini import { sampleTech } from 'utils/constants'; import UploadStatus from 'utils/upload/UploadStatus'; -import fileNameForApiV1 from 'utils/upload/fileNameForApiV1'; -import getFileTypeV2 from 'utils/getFileTypeV2'; - // If the sample name of new samples coincides with already existing // ones we should not create new samples, // just reuse their sampleIds and upload the new files @@ -116,15 +113,14 @@ const createSamples = ( createdDate, lastModified: createdDate, options, - metadata: experiment?.metadataKeys - .reduce((acc, curr) => ({ ...acc, [curr]: METADATA_DEFAULT_VALUE }), {}) || {}, - files: Object.values(files).reduce(((acc, curr) => { - const fileType = fileNameForApiV1[getFileTypeV2(curr.name, sampleTechnology)]; - - return ( - { ...acc, [fileType]: { upload: { status: UploadStatus.UPLOADING } } } - ); - }), {}), + metadata: experiment?.metadataKeys.reduce( + (acc, metadataKey) => ( + { ...acc, [metadataKey]: METADATA_DEFAULT_VALUE }), {}, + ) ?? {}, + + files: Object.keys(files).reduce(((acc, fileType) => ( + { ...acc, [fileType]: { upload: { status: UploadStatus.UPLOADING } } } + )), {}), })); dispatch({ diff --git a/src/redux/actions/samples/loadSamples.js b/src/redux/actions/samples/loadSamples.js index 73f6fc7722..1b125c3698 100644 --- a/src/redux/actions/samples/loadSamples.js +++ b/src/redux/actions/samples/loadSamples.js @@ -1,6 +1,5 @@ import fetchAPI from 'utils/http/fetchAPI'; import handleError from 'utils/http/handleError'; -import fileNameForApiV1 from 'utils/upload/fileNameForApiV1'; import { SAMPLES_LOADED, @@ -8,51 +7,43 @@ import { SAMPLES_LOADING, } from 'redux/actionTypes/samples'; -const toApiV1 = (samples, experimentId) => { - const apiV1Samples = {}; +const adaptedToRedux = (samples, experimentId) => { + const reduxSamples = {}; - const buildApiv1Files = (files) => { - const fileNames = []; + const buildReduxFiles = (files) => { const apiV1Files = {}; Object.keys(files).forEach((key) => { const fileType = files[key]?.sampleFileType; if (!fileType) throw new Error('No sample file found'); - const fileName = fileNameForApiV1[fileType]; - - fileNames.push(fileNameForApiV1[fileType]); - - apiV1Files[fileName] = { + apiV1Files[fileType] = { size: files[key].size, - valid: true, - name: fileName, upload: { status: files[key].uploadStatus, }, }; }); - return { apiV1Files, fileNames }; + return apiV1Files; }; samples.forEach((sample) => { - const { apiV1Files, fileNames } = buildApiv1Files(sample.files); - apiV1Samples[sample.id] = { + const reduxFiles = buildReduxFiles(sample.files); + reduxSamples[sample.id] = { experimentId, metadata: sample.metadata, createdDate: sample.createdAt, name: sample.name, lastModified: sample.updatedAt, - files: apiV1Files, + files: reduxFiles, type: sample.sampleTechnology, options: sample.options, - fileNames, uuid: sample.id, }; }); - return apiV1Samples; + return reduxSamples; }; const loadSamples = (experimentId) => async (dispatch) => { @@ -65,7 +56,7 @@ const loadSamples = (experimentId) => async (dispatch) => { const url = `/v2/experiments/${experimentId}/samples`; const data = await fetchAPI(url); - const samples = toApiV1(data, experimentId); + const samples = adaptedToRedux(data, experimentId); dispatch({ type: SAMPLES_LOADED, @@ -87,5 +78,3 @@ const loadSamples = (experimentId) => async (dispatch) => { }; export default loadSamples; - -export { toApiV1 }; diff --git a/src/redux/actions/samples/updateSampleFileUpload.js b/src/redux/actions/samples/updateSampleFileUpload.js index c67d613434..4bb62d42ae 100644 --- a/src/redux/actions/samples/updateSampleFileUpload.js +++ b/src/redux/actions/samples/updateSampleFileUpload.js @@ -5,7 +5,6 @@ import endUserMessages from 'utils/endUserMessages'; import fetchAPI from 'utils/http/fetchAPI'; import handleError from 'utils/http/handleError'; import UploadStatus from 'utils/upload/UploadStatus'; -import fileNameForApiV1 from 'utils/upload/fileNameForApiV1'; const updateSampleFileUpload = ( experimentId, sampleId, sampleFileId, type, uploadStatus, uploadProgress, @@ -34,8 +33,8 @@ const updateSampleFileUpload = ( type: SAMPLES_FILE_UPDATE, payload: { sampleUuid: sampleId, + sampleFileType: type, lastModified: updatedAt, - fileName: fileNameForApiV1[type], fileDiff: { upload: { status: UploadStatus.UPLOAD_ERROR } }, }, }); @@ -50,8 +49,8 @@ const updateSampleFileUpload = ( type: SAMPLES_FILE_UPDATE, payload: { sampleUuid: sampleId, + sampleFileType: type, lastModified: updatedAt, - fileName: fileNameForApiV1[type], fileDiff: { upload: { status: uploadStatus, progress: uploadProgress } }, }, }); diff --git a/src/redux/reducers/samples/initialState.js b/src/redux/reducers/samples/initialState.js index 3a37fa4720..e85bc464c8 100644 --- a/src/redux/reducers/samples/initialState.js +++ b/src/redux/reducers/samples/initialState.js @@ -9,24 +9,19 @@ const sampleTemplate = { lastModified: null, complete: false, error: false, - fileNames: [], files: {}, metadata: {}, options: {}, }; +// TODO: Update, this initial state doesn't even match the previously used structure const sampleFileTemplate = { objectKey: '', - name: null, size: 0, - mime: '', - path: '', success: false, error: false, - lastModified: '', upload: { status: null, - amplifyPromise: null, }, }; diff --git a/src/redux/reducers/samples/samplesFileUpdate.js b/src/redux/reducers/samples/samplesFileUpdate.js index ee1561f15f..befc77e855 100644 --- a/src/redux/reducers/samples/samplesFileUpdate.js +++ b/src/redux/reducers/samples/samplesFileUpdate.js @@ -2,7 +2,7 @@ import _ from 'lodash'; const samplesFileUpdate = (state, action) => { const { - sampleUuid, fileName, fileDiff, lastModified, + sampleUuid, sampleFileType, fileDiff, lastModified, } = action.payload; // There's a possible race condition where a file update can reach this place @@ -11,28 +11,21 @@ const samplesFileUpdate = (state, action) => { return state; } - const oldFile = state[sampleUuid].files?.[fileName]; + const oldFile = state[sampleUuid].files?.[sampleFileType]; let newFile = fileDiff; if (oldFile) { newFile = _.merge({}, oldFile, fileDiff); } - const newFileNames = _.cloneDeep(state[sampleUuid].fileNames); - if (!newFileNames.includes(fileName)) { - newFileNames.push(fileName); - } - return { ...state, [sampleUuid]: { ...state[sampleUuid], - fileNames: newFileNames, files: { ...state[sampleUuid].files, - [fileName]: { + [sampleFileType]: { ...newFile, - lastModified, }, }, lastModified, diff --git a/src/utils/data-management/downloadSampleFile.js b/src/utils/data-management/downloadSampleFile.js index 74807356ff..1ef1a09c42 100644 --- a/src/utils/data-management/downloadSampleFile.js +++ b/src/utils/data-management/downloadSampleFile.js @@ -1,11 +1,7 @@ import downloadFromUrl from 'utils/downloadFromUrl'; import fetchAPI from 'utils/http/fetchAPI'; -import getFileTypeV2 from 'utils/getFileTypeV2'; - -const downloadSampleFile = async (experimentId, sampleUuid, fileName, selectedTech) => { - const fileType = getFileTypeV2(fileName, selectedTech); - +const downloadSampleFile = async (experimentId, sampleUuid, fileType) => { const requestUrl = `/v2/experiments/${experimentId}/samples/${sampleUuid}/files/${fileType}/downloadUrl`; const downloadUrl = await fetchAPI(requestUrl); diff --git a/src/utils/getFileTypeV2.js b/src/utils/getFileTypeV2.js deleted file mode 100644 index 4d9a09ede5..0000000000 --- a/src/utils/getFileTypeV2.js +++ /dev/null @@ -1,34 +0,0 @@ -import { sampleTech } from 'utils/constants'; - -const fileTypesByTech = { - [sampleTech['10X']]: { - // This handling won't be necessary after the file validation is refactored - 'matrix.mtx.gz': 'matrix10x', - 'barcodes.tsv.gz': 'barcodes10x', - 'features.tsv.gz': 'features10x', - 'genes.tsv.gz': 'features10x', - 'matrix.mtx': 'matrix10x', - 'barcodes.tsv': 'barcodes10x', - 'features.tsv': 'features10x', - 'genes.tsv': 'features10x', - }, - [sampleTech.H5]: { - 'matrix.h5': '10x_h5', - }, - [sampleTech.SEURAT]: { - 'r.rds': 'seurat', - }, - [sampleTech.RHAPSODY]: { - expression_data: 'rhapsody', - }, -}; - -const getFileTypeV2 = (fileName, selectedTech) => { - const fileTypes = fileTypesByTech[selectedTech]; - - if (Object.keys(fileTypes).length === 1) return Object.values(fileTypes)[0]; - - return fileTypes[fileName]; -}; - -export default getFileTypeV2; diff --git a/src/utils/fileNames.js b/src/utils/plotCsvFilename.js similarity index 100% rename from src/utils/fileNames.js rename to src/utils/plotCsvFilename.js diff --git a/src/utils/sampleFileType.js b/src/utils/sampleFileType.js new file mode 100644 index 0000000000..54798975c1 --- /dev/null +++ b/src/utils/sampleFileType.js @@ -0,0 +1,21 @@ +const sampleFileType = { + BARCODES_10_X: 'barcodes10x', + FEATURES_10_X: 'features10x', + MATRIX_10_X: 'matrix10x', + H5_10_X: '10x_h5', + SEURAT: 'seurat', + RHAPSODY: 'rhapsody', +}; + +const fileTypeToDisplay = { + [sampleFileType.BARCODES_10_X]: 'barcodes.tsv', + [sampleFileType.FEATURES_10_X]: 'genes.tsv', + [sampleFileType.MATRIX_10_X]: 'matrix.mtx', + [sampleFileType.H5_10_X]: 'matrix.h5', + [sampleFileType.SEURAT]: 'seurat rds', + [sampleFileType.RHAPSODY]: 'expression_data.st', +}; + +export default sampleFileType; + +export { fileTypeToDisplay }; diff --git a/src/utils/upload/fileNameForApiV1.js b/src/utils/upload/fileNameForApiV1.js deleted file mode 100644 index 7031f96cb4..0000000000 --- a/src/utils/upload/fileNameForApiV1.js +++ /dev/null @@ -1,10 +0,0 @@ -const fileNameForApiV1 = { - matrix10x: 'matrix.mtx.gz', - barcodes10x: 'barcodes.tsv.gz', - features10x: 'features.tsv.gz', - seurat: 'r.rds', - rhapsody: 'expression_data.st.gz', - '10x_h5': 'matrix.h5.gz', -}; - -export default fileNameForApiV1; diff --git a/src/utils/upload/fileUploadSpecifications.js b/src/utils/upload/fileUploadSpecifications.js index 11eb4d7a25..1527af5322 100644 --- a/src/utils/upload/fileUploadSpecifications.js +++ b/src/utils/upload/fileUploadSpecifications.js @@ -1,4 +1,5 @@ import { sampleTech } from 'utils/constants'; +import sampleFileType from 'utils/sampleFileType'; const techNamesToDisplay = { [sampleTech['10X']]: '10X Chromium', @@ -31,11 +32,7 @@ const fileUploadSpecifications = { ['barcodes.tsv or barcodes.tsv.gz'], ['matrix.mtx or matrix.mtx.gz'], ], - requiredFiles: [ - { key: 'barcodes.tsv.gz', displayedName: 'barcodes.tsv' }, - { key: 'features.tsv.gz', displayedName: 'genes.tsv' }, - { key: 'matrix.mtx.gz', displayedName: 'matrix.mtx' }, - ], + requiredFiles: [sampleFileType.BARCODES_10_X, sampleFileType.FEATURES_10_X, sampleFileType.MATRIX_10_X], fileUploadParagraphs: [ 'For each sample, upload a folder containing the 3 required files. The folder\'s name will be used to name the sample in it. You can change this name later in Data Management.', 'The required files for each sample are:', @@ -44,10 +41,23 @@ const fileUploadSpecifications = { // setting to empty string allows folder upload on dropzone click webkitdirectory: '', isNameValid(fileName) { return matchFileName(fileName, this.acceptedFiles); }, - getCorrespondingName(fileName) { + getCorrespondingType(fileName) { + const fileNameToType = { + 'barcodes.tsv.gz': sampleFileType.BARCODES_10_X, + 'barcodes.tsv': sampleFileType.BARCODES_10_X, + 'features.tsv.gz': sampleFileType.FEATURES_10_X, + 'genes.tsv.gz': sampleFileType.FEATURES_10_X, + 'features.tsv': sampleFileType.FEATURES_10_X, + 'genes.tsv': sampleFileType.FEATURES_10_X, + 'matrix.mtx.gz': sampleFileType.MATRIX_10_X, + 'matrix.mtx': sampleFileType.MATRIX_10_X, + }; + const allowedNames = Array.from(this.acceptedFiles); - return allowedNames.find((allowedName) => fileName.endsWith(allowedName)); + const name = allowedNames.find((allowedName) => fileName.endsWith(allowedName)); + + return fileNameToType[name]; }, }, [sampleTech.SEURAT]: { @@ -61,9 +71,7 @@ const fileUploadSpecifications = { ['\uD83D\uDCA1sample level metadata in scdata@meta.data that groups samples in scdata$samples is auto-detected for downstream analysis.'], ['\uD83D\uDCA1if file size is over 15GB, try removing any assays not indicated above.'], ], - requiredFiles: [ - { key: 'r.rds', displayedName: 'seurat rds' }, - ], + requiredFiles: ['seurat'], fileUploadParagraphs: [ '

For your dataset, upload a single *.rds file with the Seurat object (max 15GB).

', '

The Seurat object must contain the following slots and metadata:

', @@ -76,11 +84,11 @@ const fileUploadSpecifications = { (validExtension) => fileName.endsWith(validExtension), ); }, - getCorrespondingName: () => 'r.rds', + getCorrespondingType: () => 'seurat', }, [sampleTech.RHAPSODY]: { acceptedFiles: new Set(['expression_data.st', 'expression_data.st.gz']), - requiredFiles: [{ key: 'expression_data.st.gz', displayedName: 'expression_data.st' }], + requiredFiles: ['rhapsody'], inputInfo: [ ['expression_data.st or expression_data.st.gz'], ], @@ -92,17 +100,17 @@ const fileUploadSpecifications = { dropzoneText: 'Drag and drop folders here or click to browse.', webkitdirectory: '', isNameValid: (fileName) => fileName.toLowerCase().match(/.*expression_data.st(.gz)?$/), - getCorrespondingName: (fileName) => fileName, + getCorrespondingType: () => 'rhapsody', }, [sampleTech.H5]: { acceptedFiles: new Set(['matrix.h5', 'matrix.h5.gz']), - requiredFiles: [{ key: 'matrix.h5.gz', displayedName: 'matrix.h5' }], + requiredFiles: ['10x_h5'], inputInfo: [['matrix.h5 or matrix.h5.gz']], fileUploadParagraphs: [`For each sample, upload a folder containing the h5 file. The folder's name will be used to name the sample in it. You can change this name later in Data Management.`], isNameValid: (fileName) => fileName.toLowerCase().match(/.*matrix.h5(.gz)?$/), - getCorrespondingName: (fileName) => fileName, + getCorrespondingType: () => '10x_h5', }, }; diff --git a/src/utils/upload/processUpload.js b/src/utils/upload/processUpload.js index 85b0924c7f..5659377d4d 100644 --- a/src/utils/upload/processUpload.js +++ b/src/utils/upload/processUpload.js @@ -10,7 +10,6 @@ import loadAndCompressIfNecessary from 'utils/upload/loadAndCompressIfNecessary' import { inspectFile, Verdict } from 'utils/upload/fileInspector'; import fetchAPI from 'utils/http/fetchAPI'; -import getFileTypeV2 from 'utils/getFileTypeV2'; import { sampleTech } from 'utils/constants'; import fileUploadSpecifications from 'utils/upload/fileUploadSpecifications'; import processMultipartUpload from 'utils/upload/processMultipartUpload'; @@ -63,9 +62,9 @@ const prepareAndUploadFileToS3 = async ( return parts; }; -const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch, selectedTech) => { - const fileType = getFileTypeV2(file.name, selectedTech); - +const createAndUploadSampleFile = async ( + file, fileType, experimentId, sampleId, dispatch, selectedTech, +) => { const abortController = new AbortController(); let sampleFileId; @@ -85,6 +84,11 @@ const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch, return; } + // Take the fileName now because after loadAndCompressIfNecessary the name could + // be lost in the compression. If it is compressed fileObject becomes a uInt8Array + // instead of the fileReader metadata object that it is now + const fileName = file.fileObject.name; + if (!file.compressed) { try { file.fileObject = await loadAndCompressIfNecessary(file, () => { @@ -111,7 +115,7 @@ const createAndUploadSampleFile = async (file, experimentId, sampleId, dispatch, experimentId, sampleFileId, file.size, - getMetadata(file, selectedTech), + getMetadata(fileName, selectedTech), ); const updateSampleFileUploadProgress = (status, percentProgress = 0) => dispatch( @@ -140,29 +144,31 @@ const beginSampleFileUpload = async (experimentId, sampleFileId, size, metadata) }, ); -const getMetadata = (file, selectedTech) => { +const getMetadata = (fileName, selectedTech) => { const metadata = {}; if (selectedTech === sampleTech['10X']) { - if (file.name.includes('genes')) { + if (fileName.includes('genes')) { metadata.cellranger_version = 'v2'; - } else if (file.name.includes('features')) { + } else if (fileName.includes('features')) { metadata.cellranger_version = 'v3'; } } + return metadata; }; +const getFileSampleAndName = (filePath) => { + const [sample, name] = _.takeRight(filePath.split('/'), 2); + + return { sample, name }; +}; + const processUpload = async (filesList, technology, samples, experimentId, dispatch) => { // First use map to make it easy to add files in the already existing sample entry const samplesMap = filesList.reduce((acc, file) => { - const pathToArray = file.name.trim().replace(/[\s]{2,}/ig, ' ').split('/'); - - const sampleName = pathToArray[0]; - const fileName = fileUploadSpecifications[technology].getCorrespondingName(_.last(pathToArray)); + const { sample: sampleName, name } = getFileSampleAndName(file.fileObject.path.replace(/[\s]{2,}/ig, ' ')); - // Update the file name so that instead of being saved as - // e.g. WT13/matrix.tsv.gz, we save it as matrix.tsv.gz - file.name = fileName; + const fileType = fileUploadSpecifications[technology].getCorrespondingType(name); const sampleUuid = Object.values(samples).filter( (s) => s.name === sampleName @@ -176,7 +182,7 @@ const processUpload = async (filesList, technology, samples, experimentId, dispa uuid: sampleUuid, files: { ...acc[sampleName]?.files, - [fileName]: file, + [fileType]: file, }, }, }; @@ -202,10 +208,11 @@ const processUpload = async (filesList, technology, samples, experimentId, dispa const promises = []; validSamplesList.forEach(([name, sample]) => { - Object.values(sample.files).forEach((file) => { + Object.entries(sample.files).forEach(([type, file]) => { promises.push( async () => await createAndUploadSampleFile( file, + type, experimentId, sampleIdsByName[name], dispatch, @@ -238,12 +245,6 @@ const processUpload = async (filesList, technology, samples, experimentId, dispa const fileObjectToFileRecord = async (fileObject, technology) => { // This is the first stage in uploading a file. - // if the file has a path, trim to just the file and its folder. - // otherwise simply use its name - const filename = (fileObject.path) - ? _.takeRight(fileObject.path.split('/'), 2).join('/') - : fileObject.name; - const verdict = await inspectFile(fileObject, technology); let error = ''; @@ -254,7 +255,6 @@ const fileObjectToFileRecord = async (fileObject, technology) => { } return { - name: filename, fileObject, size: fileObject.size, path: fileObject.path, @@ -262,7 +262,7 @@ const fileObjectToFileRecord = async (fileObject, technology) => { status: UploadStatus.UPLOADING, progress: 0, }, - valid: !error, + errors: error, compressed: verdict === Verdict.VALID_ZIPPED, }; @@ -272,6 +272,7 @@ export { fileObjectToFileRecord, createAndUploadSampleFile, prepareAndUploadFileToS3, + getFileSampleAndName, }; export default processUpload; diff --git a/src/utils/upload/validate10x.js b/src/utils/upload/validate10x.js index 126b5cc419..8aff4938ac 100644 --- a/src/utils/upload/validate10x.js +++ b/src/utils/upload/validate10x.js @@ -74,22 +74,14 @@ const getMatrixHead = async (matrix) => { return matrixHeader; }; -const getSampleFiles = (sample) => { - const barcodes = sample.files['barcodes.tsv.gz'] || sample.files['barcodes.tsv']; - const features = sample.files['features.tsv.gz'] || sample.files['features.tsv'] || sample.files['genes.tsv.gz'] || sample.files['genes.tsv']; - const matrix = sample.files['matrix.mtx.gz'] || sample.files['matrix.mtx']; - - return { barcodes, features, matrix }; -}; - const validateSampleCompleteness = async (sampleFiles) => { - const { barcodes, features, matrix } = sampleFiles; + const { barcodes10x, features10x, matrix10x } = sampleFiles; const missingFiles = []; - if (!barcodes) missingFiles.push('barcodes'); - if (!features) missingFiles.push('features'); - if (!matrix) missingFiles.push('matrix'); + if (!barcodes10x) missingFiles.push('barcodes'); + if (!features10x) missingFiles.push('features'); + if (!matrix10x) missingFiles.push('matrix'); if (missingFiles.length) { throw new SampleValidationError(errorMessages.missingFiles(missingFiles)); @@ -97,9 +89,9 @@ const validateSampleCompleteness = async (sampleFiles) => { }; const validateMatrixFormat = async (sampleFiles) => { - const { matrix } = sampleFiles; + const { matrix10x } = sampleFiles; - const matrixHead = await getMatrixHead(matrix); + const matrixHead = await getMatrixHead(matrix10x); // Reject sample if type of count matrix is "array" not "coordinate" // See https://networkrepository.com/mtx-matrix-market-format.html @@ -110,18 +102,18 @@ const validateMatrixFormat = async (sampleFiles) => { }; const validateFileSizes = async (sampleFiles) => { - const { barcodes, features, matrix } = sampleFiles; + const { barcodes10x, features10x, matrix10x } = sampleFiles; const errors = []; - const matrixHead = await getMatrixHead(matrix); + const matrixHead = await getMatrixHead(matrix10x); const [ expectedNumFeatures, expectedNumBarcodes, ] = extractSampleSizes(matrixHead); - const numBarcodesFound = await getNumLines(barcodes); - const numFeaturesFound = await getNumLines(features); + const numBarcodesFound = await getNumLines(barcodes10x); + const numFeaturesFound = await getNumLines(features10x); if (numBarcodesFound === expectedNumFeatures && numFeaturesFound === expectedNumBarcodes) { @@ -144,11 +136,9 @@ const validateFileSizes = async (sampleFiles) => { }; const validate10x = async (sample) => { - const sampleFiles = getSampleFiles(sample); - - await validateSampleCompleteness(sampleFiles); - await validateMatrixFormat(sampleFiles); - await validateFileSizes(sampleFiles); + await validateSampleCompleteness(sample.files); + await validateMatrixFormat(sample.files); + await validateFileSizes(sample.files); }; export default validate10x; diff --git a/src/utils/upload/validateRhapsody.js b/src/utils/upload/validateRhapsody.js index 2badd2350e..c731ff26f3 100644 --- a/src/utils/upload/validateRhapsody.js +++ b/src/utils/upload/validateRhapsody.js @@ -6,8 +6,7 @@ const columnsToSearch = [ ]; const validateRhapsody = async (sample) => { - const fileObjectKey = Object.keys(sample.files).filter((key) => key.toLowerCase().includes('expression_data.st'))[0]; - const { compressed, fileObject } = sample.files[fileObjectKey]; + const { compressed, fileObject } = sample.files.rhapsody; const fileArrBuffer = await fileObject.slice(0, 800).arrayBuffer(); const file = compressed