From dd950a46c39d07cff07bc0339a736177723389df Mon Sep 17 00:00:00 2001 From: Nick Grosenbacher Date: Tue, 27 Jun 2023 14:29:23 -0400 Subject: [PATCH 1/3] PORTALS-2701 - Export selection to CAVATICA - Feature: Selected rows can be exported to CAVATICA - Refactor: Add ExportToCavaticaModal and story - Refactor: Added UI-only RowSelectionUI component and story, used by RowSelectionControls --- .../mocks/mockFileViewQuery.ts | 2603 +++++++++++++++++ packages/synapse-react-client/package.json | 1 + .../ConfirmationDialog/ConfirmationDialog.tsx | 14 +- .../components/QueryVisualizationWrapper.tsx | 14 +- .../RowSelection/RowSelectionControls.tsx | 71 + .../RowSelection/RowSelectionUI.tsx | 60 + .../SynapseTable/RowSelectionControls.tsx | 71 - .../SendToCavaticaConfirmationDialog.tsx | 79 +- .../SynapseTable/TopLevelControls.tsx | 66 +- .../components/widgets/ElementWithTooltip.tsx | 10 +- .../stories/QueryWrapperPlotNav.stories.ts | 1 + .../stories/RowSelection.stories.tsx | 42 + ...ndToCavaticaConfirmationDialog.stories.tsx | 68 + .../SendToCavaticaConfirmationDialog.test.tsx | 132 + pnpm-lock.yaml | 215 ++ 15 files changed, 3318 insertions(+), 129 deletions(-) create mode 100644 packages/synapse-react-client/mocks/mockFileViewQuery.ts create mode 100644 packages/synapse-react-client/src/components/SynapseTable/RowSelection/RowSelectionControls.tsx create mode 100644 packages/synapse-react-client/src/components/SynapseTable/RowSelection/RowSelectionUI.tsx delete mode 100644 packages/synapse-react-client/src/components/SynapseTable/RowSelectionControls.tsx create mode 100644 packages/synapse-react-client/stories/RowSelection.stories.tsx create mode 100644 packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx create mode 100644 packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx diff --git a/packages/synapse-react-client/mocks/mockFileViewQuery.ts b/packages/synapse-react-client/mocks/mockFileViewQuery.ts new file mode 100644 index 0000000000..b8cb8581c5 --- /dev/null +++ b/packages/synapse-react-client/mocks/mockFileViewQuery.ts @@ -0,0 +1,2603 @@ +import { + AsynchronousJobStatus, + QueryBundleRequest, + QueryResultBundle, +} from '@sage-bionetworks/synapse-types' + +export const mockQueryBundleRequest: QueryBundleRequest = { + entityId: 'syn11346063', + concreteType: 'org.sagebionetworks.repo.model.table.QueryBundleRequest', + partMask: 255, + query: { + sql: 'SELECT * FROM syn11346063', + includeEntityEtag: true, + selectedFacets: [], + }, +} + +export const mockQueryResultBundle: QueryResultBundle = { + concreteType: 'org.sagebionetworks.repo.model.table.QueryResultBundle', + queryCount: 182905, + queryResult: { + concreteType: 'org.sagebionetworks.repo.model.table.QueryResult', + queryResults: { + concreteType: 'org.sagebionetworks.repo.model.table.RowSet', + tableId: 'syn11346063', + etag: 'DEFAULT', + headers: [ + { name: 'id', columnType: 'ENTITYID', id: '81721' }, + { name: 'name', columnType: 'STRING', id: '81722' }, + { name: 'study', columnType: 'STRING_LIST', id: '190401' }, + { name: 'dataType', columnType: 'STRING_LIST', id: '171338' }, + { name: 'assay', columnType: 'STRING_LIST', id: '171339' }, + { name: 'organ', columnType: 'STRING', id: '168363' }, + { name: 'tissue', columnType: 'STRING_LIST', id: '124190' }, + { name: 'species', columnType: 'STRING_LIST', id: '171340' }, + { name: 'sex', columnType: 'STRING_LIST', id: '124191' }, + { name: 'consortium', columnType: 'STRING', id: '121553' }, + { name: 'grant', columnType: 'STRING_LIST', id: '124185' }, + { name: 'modelSystemName', columnType: 'STRING_LIST', id: '151953' }, + { name: 'treatmentType', columnType: 'STRING', id: '152365' }, + { name: 'specimenID', columnType: 'STRING', id: '81746' }, + { name: 'individualID', columnType: 'STRING', id: '85077' }, + { name: 'individualIdSource', columnType: 'STRING', id: '83061' }, + { name: 'specimenIdSource', columnType: 'STRING', id: '81747' }, + { name: 'resourceType', columnType: 'STRING', id: '81770' }, + { name: 'dataSubtype', columnType: 'STRING', id: '124204' }, + { name: 'metadataType', columnType: 'STRING', id: '167131' }, + { name: 'assayTarget', columnType: 'STRING', id: '117270' }, + { name: 'analysisType', columnType: 'STRING', id: '178414' }, + { name: 'cellType', columnType: 'STRING_LIST', id: '124195' }, + { name: 'nucleicAcidSource', columnType: 'STRING', id: '82254' }, + { name: 'fileFormat', columnType: 'STRING', id: '114434' }, + { name: 'group', columnType: 'STRING', id: '86204' }, + { name: 'isModelSystem', columnType: 'BOOLEAN', id: '122223' }, + { name: 'isConsortiumAnalysis', columnType: 'BOOLEAN', id: '86209' }, + { name: 'isMultiSpecimen', columnType: 'BOOLEAN', id: '116145' }, + { name: 'createdOn', columnType: 'DATE', id: '81771' }, + { name: 'createdBy', columnType: 'USERID', id: '81761' }, + { name: 'parentId', columnType: 'ENTITYID', id: '81734' }, + { name: 'currentVersion', columnType: 'INTEGER', id: '81729' }, + { name: 'benefactorId', columnType: 'ENTITYID', id: '81731' }, + { name: 'projectId', columnType: 'ENTITYID', id: '81763' }, + { name: 'modifiedOn', columnType: 'DATE', id: '81764' }, + { name: 'modifiedBy', columnType: 'USERID', id: '81765' }, + { name: 'dataFileHandleId', columnType: 'FILEHANDLEID', id: '81733' }, + { name: 'metaboliteType', columnType: 'STRING_LIST', id: '152356' }, + { name: 'chromosome', columnType: 'STRING', id: '159027' }, + { name: 'modelSystemType', columnType: 'STRING', id: '171510' }, + { name: 'libraryPrep', columnType: 'STRING', id: '180380' }, + { name: 'dataFileSizeBytes', columnType: 'INTEGER', id: '112368' }, + ], + rows: [ + { + rowId: 2426151, + versionNumber: 3, + etag: '60e64259-515c-4609-aaa5-7c2d8ba82dc4', + values: [ + 'syn2426151', + 'chr1.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398115614479', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268379', + '3420438', + '75430845', + null, + null, + null, + null, + '643156843', + ], + }, + { + rowId: 2426152, + versionNumber: 3, + etag: '9844ca5a-a422-40ec-8eab-3168769fd309', + values: [ + 'syn2426152', + 'chr2.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398115749555', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268426', + '3420438', + '75430846', + null, + null, + null, + null, + '697794692', + ], + }, + { + rowId: 2426153, + versionNumber: 3, + etag: '220e4abd-75d5-4378-990e-2497e76e113a', + values: [ + 'syn2426153', + 'chr4.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398116007551', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268473', + '3420438', + '75430848', + null, + null, + null, + null, + '586390632', + ], + }, + { + rowId: 2426154, + versionNumber: 3, + etag: 'ca15e26b-8672-4b1f-8aae-9b55852186eb', + values: [ + 'syn2426154', + 'chr8.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398116040326', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268524', + '3420438', + '75430847', + null, + null, + null, + null, + '449843238', + ], + }, + { + rowId: 2426155, + versionNumber: 3, + etag: 'dffa1f8a-6b1d-4375-bf9f-397a1fc3a9f0', + values: [ + 'syn2426155', + 'chr9.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398116179043', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268573', + '3420438', + '75430849', + null, + null, + null, + null, + '359984384', + ], + }, + { + rowId: 2426156, + versionNumber: 3, + etag: '014a90f8-dce4-47e6-b592-870f77c3cd83', + values: [ + 'syn2426156', + 'chr6.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398116185594', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268619', + '3420438', + '75430850', + null, + null, + null, + null, + '489363407', + ], + }, + { + rowId: 2426157, + versionNumber: 3, + etag: 'dd964e1b-4f4f-4150-a3a7-ab78c942958c', + values: [ + 'syn2426157', + 'chr5.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398116215452', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268665', + '3420438', + '75430851', + null, + null, + null, + null, + '519762250', + ], + }, + { + rowId: 2426182, + versionNumber: 3, + etag: '6a9f5102-9b6b-4d12-860d-5080c8c77854', + values: [ + 'syn2426182', + 'chr3.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398125236670', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268713', + '3420438', + '75430852', + null, + null, + null, + null, + '569886524', + ], + }, + { + rowId: 2426183, + versionNumber: 3, + etag: '18d3eca6-0410-41d4-83a1-cfd5d1d65e8f', + values: [ + 'syn2426183', + 'chr7.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398125267264', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268761', + '3420438', + '75430853', + null, + null, + null, + null, + '497334749', + ], + }, + { + rowId: 2426394, + versionNumber: 3, + etag: '311401ea-00de-46f0-a156-5dc761961e37', + values: [ + 'syn2426394', + 'chr14.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168073413', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268810', + '3420438', + '75430854', + null, + null, + null, + null, + '259647640', + ], + }, + { + rowId: 2426395, + versionNumber: 3, + etag: '21a488fd-15c7-47e4-b797-6eac956db942', + values: [ + 'syn2426395', + 'chr10.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168096106', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268857', + '3420438', + '75430855', + null, + null, + null, + null, + '401109571', + ], + }, + { + rowId: 2426396, + versionNumber: 3, + etag: '095cbea0-5c13-4063-be46-d68090665b89', + values: [ + 'syn2426396', + 'chr15.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168147582', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268901', + '3420438', + '75430856', + null, + null, + null, + null, + '257754155', + ], + }, + { + rowId: 2426397, + versionNumber: 3, + etag: '0c423237-6309-45c5-8919-e5eb27a8bd54', + values: [ + 'syn2426397', + 'chr12.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168193343', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580268959', + '3420438', + '75430857', + null, + null, + null, + null, + '373221867', + ], + }, + { + rowId: 2426398, + versionNumber: 3, + etag: '5229a537-8a98-4db7-a35e-68d2f2dd7b5a', + values: [ + 'syn2426398', + 'chr16.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168230194', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269006', + '3420438', + '75430860', + null, + null, + null, + null, + '291524625', + ], + }, + { + rowId: 2426399, + versionNumber: 3, + etag: 'f23b1d51-69cc-4e3a-8ff2-6f4ad7b0288b', + values: [ + 'syn2426399', + 'chr13.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168273179', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269056', + '3420438', + '75430858', + null, + null, + null, + null, + '269148012', + ], + }, + { + rowId: 2426400, + versionNumber: 3, + etag: '74dba364-f3ff-4c9f-bb45-568c176b460f', + values: [ + 'syn2426400', + 'chr21.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168543800', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269104', + '3420438', + '75430859', + null, + null, + null, + null, + '117296000', + ], + }, + { + rowId: 2426401, + versionNumber: 3, + etag: '339ad366-5227-44f4-84e1-148878601279', + values: [ + 'syn2426401', + 'chr17.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168556863', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269151', + '3420438', + '75430861', + null, + null, + null, + null, + '253506056', + ], + }, + { + rowId: 2426402, + versionNumber: 3, + etag: '40573d60-37af-4c50-af64-bb09eeb38e85', + values: [ + 'syn2426402', + 'chr11.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168573585', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269196', + '3420438', + '75430863', + null, + null, + null, + null, + '386920788', + ], + }, + { + rowId: 2426403, + versionNumber: 3, + etag: '82d50f13-767c-4649-b906-907d753ddd57', + values: [ + 'syn2426403', + 'chr22.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168587155', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269242', + '3420438', + '75430862', + null, + null, + null, + null, + '130131605', + ], + }, + { + rowId: 2426404, + versionNumber: 3, + etag: 'f2a833fd-331d-419b-84f3-5f7e47b8ab2a', + values: [ + 'syn2426404', + 'chr18.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168625814', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269290', + '3420438', + '75430865', + null, + null, + null, + null, + '228889490', + ], + }, + { + rowId: 2426405, + versionNumber: 3, + etag: '36ffe08f-c147-4dd7-982e-4dc64514e3e8', + values: [ + 'syn2426405', + 'chr19.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398168700908', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269335', + '3420438', + '75430864', + null, + null, + null, + null, + '210695647', + ], + }, + { + rowId: 2426406, + versionNumber: 3, + etag: '9135cb34-fc8c-4129-b841-0509a9727776', + values: [ + 'syn2426406', + 'chr20.chop.dosage.gz', + '["ROSMAP"]', + '["genomicVariants"]', + '["snpArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'genotype imputation', + null, + 'bulk cell', + 'txt', + null, + null, + null, + 'true', + '1398169135526', + '3320447', + 'syn2426141', + '3', + 'syn2580853', + 'syn2580853', + '1634580269397', + '3420438', + '75430868', + null, + null, + null, + null, + '179380534', + ], + }, + { + rowId: 3168763, + versionNumber: 2, + etag: 'a8a11601-6261-45e1-8506-3963759285d3', + values: [ + 'syn3168763', + 'ROSMAP_arrayMethylation_imputed.tsv.gz', + '["ROSMAP"]', + '["epigenetics"]', + '["methylationArray"]', + 'brain', + '["dorsolateral prefrontal cortex"]', + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'analysis', + 'processed', + null, + null, + 'DNA methylation imputation', + null, + 'bulk cell', + 'tsv', + null, + null, + null, + 'true', + '1423679481193', + '3323072', + 'syn3157275', + '2', + 'syn2580853', + 'syn2580853', + '1634580269445', + '3420438', + '75431665', + null, + null, + null, + null, + '1037160127', + ], + }, + { + rowId: 3168775, + versionNumber: 2, + etag: '3ad6fd75-910b-440e-b240-c9d65bc1cf9c', + values: [ + 'syn3168775', + 'ROSMAP_arrayMethylation_metaData.tsv', + '["ROSMAP"]', + '["epigenetics"]', + '["methylationArray"]', + null, + null, + '["Human"]', + null, + 'AMP-AD', + '["U01AG046152"]', + null, + null, + null, + null, + null, + null, + 'experimentalData', + 'processed', + null, + null, + null, + null, + null, + 'tsv', + null, + null, + null, + 'true', + '1423681235083', + '3323072', + 'syn3157275', + '2', + 'syn2580853', + 'syn2580853', + '1634580269496', + '3420438', + '75431668', + null, + null, + null, + null, + '34082484', + ], + }, + { + rowId: 3169023, + versionNumber: 3, + etag: '994977fd-3e5c-45a6-b266-f3ae1eb3ede8', + values: [ + 'syn3169023', + 'AMP-AD_HBTRC_MSSM_Agilent44Karray_PFC_AgeCorrected_all', + '["HBTRC"]', + '["geneExpression"]', + '["rnaArray"]', + 'brain', + '["dorsolateral prefrontal cortex"]', + '["Human"]', + null, + 'AMP-AD', + '["U01AG046170"]', + null, + null, + null, + null, + null, + null, + 'experimentalData', + null, + null, + null, + null, + null, + null, + 'tsv', + null, + 'false', + null, + 'true', + '1423685596259', + '3323072', + 'syn20808200', + '3', + 'syn2580853', + 'syn2580853', + '1634580269544', + '3420438', + '75431673', + null, + null, + null, + null, + '176298459', + ], + }, + ], + }, + }, + selectColumns: [ + { name: 'id', columnType: 'ENTITYID', id: '81721' }, + { name: 'name', columnType: 'STRING', id: '81722' }, + { name: 'study', columnType: 'STRING_LIST', id: '190401' }, + { name: 'dataType', columnType: 'STRING_LIST', id: '171338' }, + { name: 'assay', columnType: 'STRING_LIST', id: '171339' }, + { name: 'organ', columnType: 'STRING', id: '168363' }, + { name: 'tissue', columnType: 'STRING_LIST', id: '124190' }, + { name: 'species', columnType: 'STRING_LIST', id: '171340' }, + { name: 'sex', columnType: 'STRING_LIST', id: '124191' }, + { name: 'consortium', columnType: 'STRING', id: '121553' }, + { name: 'grant', columnType: 'STRING_LIST', id: '124185' }, + { name: 'modelSystemName', columnType: 'STRING_LIST', id: '151953' }, + { name: 'treatmentType', columnType: 'STRING', id: '152365' }, + { name: 'specimenID', columnType: 'STRING', id: '81746' }, + { name: 'individualID', columnType: 'STRING', id: '85077' }, + { name: 'individualIdSource', columnType: 'STRING', id: '83061' }, + { name: 'specimenIdSource', columnType: 'STRING', id: '81747' }, + { name: 'resourceType', columnType: 'STRING', id: '81770' }, + { name: 'dataSubtype', columnType: 'STRING', id: '124204' }, + { name: 'metadataType', columnType: 'STRING', id: '167131' }, + { name: 'assayTarget', columnType: 'STRING', id: '117270' }, + { name: 'analysisType', columnType: 'STRING', id: '178414' }, + { name: 'cellType', columnType: 'STRING_LIST', id: '124195' }, + { name: 'nucleicAcidSource', columnType: 'STRING', id: '82254' }, + { name: 'fileFormat', columnType: 'STRING', id: '114434' }, + { name: 'group', columnType: 'STRING', id: '86204' }, + { name: 'isModelSystem', columnType: 'BOOLEAN', id: '122223' }, + { name: 'isConsortiumAnalysis', columnType: 'BOOLEAN', id: '86209' }, + { name: 'isMultiSpecimen', columnType: 'BOOLEAN', id: '116145' }, + { name: 'createdOn', columnType: 'DATE', id: '81771' }, + { name: 'createdBy', columnType: 'USERID', id: '81761' }, + { name: 'parentId', columnType: 'ENTITYID', id: '81734' }, + { name: 'currentVersion', columnType: 'INTEGER', id: '81729' }, + { name: 'benefactorId', columnType: 'ENTITYID', id: '81731' }, + { name: 'projectId', columnType: 'ENTITYID', id: '81763' }, + { name: 'modifiedOn', columnType: 'DATE', id: '81764' }, + { name: 'modifiedBy', columnType: 'USERID', id: '81765' }, + { name: 'dataFileHandleId', columnType: 'FILEHANDLEID', id: '81733' }, + { name: 'metaboliteType', columnType: 'STRING_LIST', id: '152356' }, + { name: 'chromosome', columnType: 'STRING', id: '159027' }, + { name: 'modelSystemType', columnType: 'STRING', id: '171510' }, + { name: 'libraryPrep', columnType: 'STRING', id: '180380' }, + { name: 'dataFileSizeBytes', columnType: 'INTEGER', id: '112368' }, + ], + maxRowsPerPage: 41, + columnModels: [ + { id: '81721', name: 'id', columnType: 'ENTITYID' }, + { id: '81722', name: 'name', columnType: 'STRING', maximumSize: 256 }, + { + id: '190401', + name: 'study', + columnType: 'STRING_LIST', + maximumSize: 300, + maximumListLength: 20, + facetType: 'enumeration', + }, + { + id: '171338', + name: 'dataType', + columnType: 'STRING_LIST', + maximumSize: 43, + maximumListLength: 10, + facetType: 'enumeration', + }, + { + id: '171339', + name: 'assay', + columnType: 'STRING_LIST', + maximumSize: 40, + maximumListLength: 10, + facetType: 'enumeration', + }, + { + id: '168363', + name: 'organ', + columnType: 'STRING', + maximumSize: 15, + facetType: 'enumeration', + }, + { + id: '124190', + name: 'tissue', + columnType: 'STRING_LIST', + maximumSize: 100, + maximumListLength: 10, + facetType: 'enumeration', + }, + { + id: '171340', + name: 'species', + columnType: 'STRING_LIST', + maximumSize: 23, + maximumListLength: 10, + facetType: 'enumeration', + }, + { + id: '124191', + name: 'sex', + columnType: 'STRING_LIST', + maximumSize: 12, + maximumListLength: 2, + facetType: 'enumeration', + }, + { + id: '121553', + name: 'consortium', + columnType: 'STRING', + maximumSize: 40, + facetType: 'enumeration', + }, + { + id: '124185', + name: 'grant', + columnType: 'STRING_LIST', + maximumSize: 76, + maximumListLength: 10, + }, + { + id: '151953', + name: 'modelSystemName', + columnType: 'STRING_LIST', + maximumSize: 100, + maximumListLength: 20, + facetType: 'enumeration', + }, + { + id: '152365', + name: 'treatmentType', + columnType: 'STRING', + maximumSize: 50, + }, + { + id: '81746', + name: 'specimenID', + columnType: 'STRING', + maximumSize: 66, + }, + { + id: '85077', + name: 'individualID', + columnType: 'STRING', + maximumSize: 40, + }, + { + id: '83061', + name: 'individualIdSource', + columnType: 'STRING', + maximumSize: 50, + facetType: 'enumeration', + }, + { + id: '81747', + name: 'specimenIdSource', + columnType: 'STRING', + maximumSize: 19, + }, + { + id: '81770', + name: 'resourceType', + columnType: 'STRING', + maximumSize: 16, + facetType: 'enumeration', + }, + { + id: '124204', + name: 'dataSubtype', + columnType: 'STRING', + maximumSize: 50, + facetType: 'enumeration', + }, + { + id: '167131', + name: 'metadataType', + columnType: 'STRING', + maximumSize: 22, + facetType: 'enumeration', + }, + { + id: '117270', + name: 'assayTarget', + columnType: 'STRING', + maximumSize: 8, + }, + { + id: '178414', + name: 'analysisType', + columnType: 'STRING', + maximumSize: 55, + facetType: 'enumeration', + }, + { + id: '124195', + name: 'cellType', + columnType: 'STRING_LIST', + maximumSize: 75, + maximumListLength: 5, + facetType: 'enumeration', + }, + { + id: '82254', + name: 'nucleicAcidSource', + columnType: 'STRING', + maximumSize: 14, + facetType: 'enumeration', + }, + { + id: '114434', + name: 'fileFormat', + columnType: 'STRING', + maximumSize: 23, + facetType: 'enumeration', + }, + { id: '86204', name: 'group', columnType: 'STRING', maximumSize: 13 }, + { + id: '122223', + name: 'isModelSystem', + columnType: 'BOOLEAN', + facetType: 'enumeration', + }, + { id: '86209', name: 'isConsortiumAnalysis', columnType: 'BOOLEAN' }, + { + id: '116145', + name: 'isMultiSpecimen', + columnType: 'BOOLEAN', + facetType: 'enumeration', + }, + { id: '81771', name: 'createdOn', columnType: 'DATE' }, + { id: '81761', name: 'createdBy', columnType: 'USERID' }, + { id: '81734', name: 'parentId', columnType: 'ENTITYID' }, + { id: '81729', name: 'currentVersion', columnType: 'INTEGER' }, + { id: '81731', name: 'benefactorId', columnType: 'ENTITYID' }, + { id: '81763', name: 'projectId', columnType: 'ENTITYID' }, + { id: '81764', name: 'modifiedOn', columnType: 'DATE' }, + { id: '81765', name: 'modifiedBy', columnType: 'USERID' }, + { id: '81733', name: 'dataFileHandleId', columnType: 'FILEHANDLEID' }, + { + id: '152356', + name: 'metaboliteType', + columnType: 'STRING_LIST', + maximumSize: 40, + maximumListLength: 10, + facetType: 'enumeration', + }, + { + id: '159027', + name: 'chromosome', + columnType: 'STRING', + maximumSize: 15, + facetType: 'enumeration', + }, + { + id: '171510', + name: 'modelSystemType', + columnType: 'STRING', + maximumSize: 50, + facetType: 'enumeration', + }, + { + id: '180380', + name: 'libraryPrep', + columnType: 'STRING', + maximumSize: 50, + facetType: 'enumeration', + }, + { id: '112368', name: 'dataFileSizeBytes', columnType: 'INTEGER' }, + ], + facets: [ + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'study', + facetType: 'enumeration', + facetValues: [ + { value: 'ROSMAP', count: 33340, isSelected: false }, + { value: 'WGS_Harmonization', count: 29432, isSelected: false }, + { value: 'AD_CrossSpecies', count: 20630, isSelected: false }, + { value: 'AMP-AD_DiverseCohorts', count: 17563, isSelected: false }, + { value: 'rnaSeqReprocessing', count: 12344, isSelected: false }, + { value: 'LBP', count: 7841, isSelected: false }, + { value: 'MSBB', count: 6881, isSelected: false }, + { value: 'TWAS', count: 6842, isSelected: false }, + { value: 'SUNYStrokeModel', count: 5954, isSelected: false }, + { + value: 'VirusResilience_Mayo.MSBB.ROSMAP', + count: 5656, + isSelected: false, + }, + { value: 'SEA-AD', count: 2368, isSelected: false }, + { value: 'SMIB-AD', count: 2160, isSelected: false }, + { value: 'MSBB_ArrayTissuePanel', count: 2120, isSelected: false }, + { value: 'RR_APOE4', count: 2061, isSelected: false }, + { value: 'UCSF_MAC', count: 1974, isSelected: false }, + { value: 'StJude_BannerSun', count: 1831, isSelected: false }, + { + value: 'ROSMAP_CognitiveResilience', + count: 1565, + isSelected: false, + }, + { value: 'HBTRC', count: 1561, isSelected: false }, + { value: 'MayoRNAseq', count: 1214, isSelected: false }, + { + value: 'Jax.IU.Pitt_MicrobiomePilot', + count: 1155, + isSelected: false, + }, + { + value: 'Jax.IU.Pitt_APOE4.Trem2.R47H', + count: 921, + isSelected: false, + }, + { value: 'UCI_5XFAD', count: 893, isSelected: false }, + { value: 'SuperAgerEpiMap', count: 865, isSelected: false }, + { value: 'MCSA', count: 848, isSelected: false }, + { value: 'Banner', count: 749, isSelected: false }, + { value: 'BCM-DMAS', count: 739, isSelected: false }, + { value: 'Jax.IU.Pitt_hTau_Trem2', count: 603, isSelected: false }, + { value: 'snRNAseqPFC_BA10', count: 591, isSelected: false }, + { value: 'APOE-TR', count: 581, isSelected: false }, + { value: 'FreshMicro', count: 581, isSelected: false }, + { value: 'EmoryDrosophilaTau', count: 515, isSelected: false }, + { value: 'ROSMAP_CellTypeSpecificHA', count: 513, isSelected: false }, + { value: 'AD-BXD', count: 481, isSelected: false }, + { value: 'AD_CrossSpecies ', count: 470, isSelected: false }, + { value: 'omicsADDS', count: 455, isSelected: false }, + { value: 'MOA-PAD', count: 449, isSelected: false }, + { value: 'UPP', count: 440, isSelected: false }, + { value: 'MC-BrAD', count: 439, isSelected: false }, + { value: 'Jax.IU.Pitt_APP.PS1', count: 390, isSelected: false }, + { value: 'ROSMAP_MammillaryBody', count: 388, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 334, + isSelected: false, + }, + { value: 'UCI_3xTg-AD', count: 325, isSelected: false }, + { value: 'VMC', count: 307, isSelected: false }, + { value: 'ADAMTS7', count: 294, isSelected: false }, + { value: 'TAUAPPms', count: 258, isSelected: false }, + { + value: 'Jax.IU.Pitt_StrainValidation', + count: 254, + isSelected: false, + }, + { value: 'UCI_Trem2-R47H_NSS', count: 250, isSelected: false }, + { value: 'Emory_Vascular', count: 217, isSelected: false }, + { value: 'MayoPilotRNAseq', count: 202, isSelected: false }, + { value: 'HBI_scRNAseq', count: 195, isSelected: false }, + { value: 'MC-CAA', count: 189, isSelected: false }, + { + value: 'MODEL-AD_JAX_GWAS_Gene_Survey', + count: 185, + isSelected: false, + }, + { value: 'snRNAseqAD_TREM2', count: 165, isSelected: false }, + { value: 'Jax.IU.Pitt_5XFAD', count: 162, isSelected: false }, + { value: 'ROSMAP-IN', count: 140, isSelected: false }, + { value: 'ROSMAP_nucleus_hashing', count: 127, isSelected: false }, + { value: 'Plxnb1_KO', count: 126, isSelected: false }, + { value: 'VirusResilience_LCL', count: 124, isSelected: false }, + { value: 'RNAseq_Harmonization', count: 117, isSelected: false }, + { value: 'MayoHippocampus', count: 113, isSelected: false }, + { value: 'BLSA', count: 111, isSelected: false }, + { value: 'UCI_PrimaryScreen', count: 107, isSelected: false }, + { + value: 'Jax.IU.Pitt.Proteomics_Metabolomics_Pilot', + count: 105, + isSelected: false, + }, + { value: 'UCI_Trem2_Cuprizone', count: 105, isSelected: false }, + { value: 'miR155', count: 101, isSelected: false }, + { + value: 'scRNAseq_microglia_wild_ADmice', + count: 101, + isSelected: false, + }, + { value: 'LillyMicroglia', count: 100, isSelected: false }, + { value: 'MSDM', count: 100, isSelected: false }, + { value: 'GJA1_deficiency', count: 99, isSelected: false }, + { value: 'TyrobpKO', count: 96, isSelected: false }, + { value: 'UCI_hAbeta_KI', count: 96, isSelected: false }, + { value: 'ACT', count: 72, isSelected: false }, + { value: 'Aging-PheWAS', count: 68, isSelected: false }, + { value: 'rnaSeqSampleSwap', count: 67, isSelected: false }, + { value: 'ADMC_ADNI1', count: 63, isSelected: false }, + { + value: 'Jax.IU.Pitt_Levetiracetam_5XFAD', + count: 58, + isSelected: false, + }, + { value: 'APOEPSC', count: 57, isSelected: false }, + { value: 'MCMPS', count: 57, isSelected: false }, + { value: 'TASTPM', count: 51, isSelected: false }, + { value: 'UFLOR_ABI3_GNGT2', count: 51, isSelected: false }, + { value: 'Jax.IU.Pitt_PrimaryScreen', count: 50, isSelected: false }, + { value: 'Emory_ADRC', count: 48, isSelected: false }, + { value: 'ACOM', count: 45, isSelected: false }, + { value: 'HDAC1-cKOBrain', count: 43, isSelected: false }, + { value: 'MSSMiPSC', count: 38, isSelected: false }, + { value: 'DiCAD', count: 37, isSelected: false }, + { value: 'DiseasePseudotime', count: 36, isSelected: false }, + { value: 'MSMM', count: 34, isSelected: false }, + { value: 'SY5Y_Emory', count: 32, isSelected: false }, + { value: 'U1-70_PrimaryCellCulture', count: 29, isSelected: false }, + { value: 'UPennPilot', count: 29, isSelected: false }, + { value: 'ADMC_ADNI2-GO', count: 28, isSelected: false }, + { value: 'Abeta_microglia', count: 27, isSelected: false }, + { value: 'iPSCMicroglia', count: 27, isSelected: false }, + { + value: 'Jax.IU.Pitt_Verubecestat_5XFAD', + count: 27, + isSelected: false, + }, + { value: 'iPSCAstrocytes', count: 26, isSelected: false }, + { value: 'TyrobpKO_AppPs1', count: 25, isSelected: false }, + { value: 'ADMC_UPenn', count: 24, isSelected: false }, + { value: 'TauD35', count: 22, isSelected: false }, + { value: 'UFL_Cxcl10', count: 21, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'dataType', + facetType: 'enumeration', + facetValues: [ + { value: 'geneExpression', count: 87275, isSelected: false }, + { value: 'genomicVariants', count: 50183, isSelected: false }, + { value: 'epigenetics', count: 27064, isSelected: false }, + { value: 'proteomics', count: 9349, isSelected: false }, + { value: 'behavior process', count: 3803, isSelected: false }, + { value: 'electrophysiology', count: 2120, isSelected: false }, + { value: 'metagenomics', count: 1153, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 623, + isSelected: false, + }, + { value: 'analysis', count: 470, isSelected: false }, + { value: 'immunoassay', count: 456, isSelected: false }, + { value: 'metabolomics', count: 191, isSelected: false }, + { value: 'clinical', count: 113, isSelected: false }, + { value: 'image', count: 102, isSelected: false }, + { value: 'gene expression', count: 30, isSelected: false }, + { value: 'Pharmacokinetic Study', count: 8, isSelected: false }, + { value: 'demographic', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'assay', + facetType: 'enumeration', + facetValues: [ + { value: 'rnaSeq', count: 76807, isSelected: false }, + { value: 'wholeGenomeSeq', count: 46194, isSelected: false }, + { value: 'methylationArray', count: 22471, isSelected: false }, + { value: 'TMT quantitation', count: 7395, isSelected: false }, + { value: 'scrnaSeq', count: 6372, isSelected: false }, + { + value: 'active avoidance learning behavior', + count: 3496, + isSelected: false, + }, + { value: 'rnaArray', count: 2182, isSelected: false }, + { value: 'ATACSeq', count: 1902, isSelected: false }, + { value: 'snpArray', count: 1722, isSelected: false }, + { value: 'ChIPSeq', count: 1689, isSelected: false }, + { value: 'scwholeGenomeSeq', count: 1633, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 1537, + isSelected: false, + }, + { value: 'snrnaSeq', count: 1467, isSelected: false }, + { value: '16SrRNAseq', count: 1153, isSelected: false }, + { value: 'LFP', count: 1106, isSelected: false }, + { + value: 'label free mass spectrometry', + count: 1015, + isSelected: false, + }, + { value: 'LTP', count: 1010, isSelected: false }, + { value: 'snATACSeq', count: 926, isSelected: false }, + { value: 'LC-MSMS', count: 635, isSelected: false }, + { value: 'exomeSeq', count: 625, isSelected: false }, + { value: 'immunofluorescence', count: 418, isSelected: false }, + { value: 'TMT quantification', count: 302, isSelected: false }, + { value: 'novelty response behavior', count: 275, isSelected: false }, + { value: 'mRNAcounts', count: 95, isSelected: false }, + { value: 'Laser Speckle Imaging', count: 73, isSelected: false }, + { value: 'HI-C', count: 72, isSelected: false }, + { value: 'Biocrates p180', count: 50, isSelected: false }, + { value: 'UPLC-MSMS', count: 46, isSelected: false }, + { value: 'long-read rnaSeq', count: 36, isSelected: false }, + { value: 'FIA-MSMS', count: 35, isSelected: false }, + { value: 'CITESeq', count: 20, isSelected: false }, + { value: 'electrochemiluminescence', count: 17, isSelected: false }, + { value: 'LC-MS', count: 17, isSelected: false }, + { value: 'Nightingale NMR', count: 17, isSelected: false }, + { value: 'ELISA', count: 12, isSelected: false }, + { + value: 'Positron Emission Tomography', + count: 11, + isSelected: false, + }, + { value: 'MRI', count: 10, isSelected: false }, + { value: 'autoradiography', count: 8, isSelected: false }, + { value: '', count: 5, isSelected: false }, + { value: 'Baker Lipidomics', count: 5, isSelected: false }, + { value: 'Metabolon', count: 5, isSelected: false }, + { value: 'open field test', count: 5, isSelected: false }, + { value: 'polymeraseChainReaction', count: 5, isSelected: false }, + { value: 'rotarod performance test', count: 5, isSelected: false }, + { value: 'UPLC-ESI-QTOF-MS', count: 5, isSelected: false }, + { value: 'Biocrates Bile Acids', count: 4, isSelected: false }, + { value: 'bisulfiteSeq', count: 4, isSelected: false }, + { value: 'frailty assessment', count: 4, isSelected: false }, + { value: 'LC-SRM', count: 4, isSelected: false }, + { value: 'SiMoA', count: 4, isSelected: false }, + { value: 'spontaneous alternation', count: 4, isSelected: false }, + { + value: 'contextual conditioning behavior', + count: 3, + isSelected: false, + }, + { value: 'MDMS-SL', count: 3, isSelected: false }, + { value: 'mirnaArray', count: 3, isSelected: false }, + { value: 'westernBlot', count: 3, isSelected: false }, + { value: 'Blood Chemistry Measurement', count: 2, isSelected: false }, + { value: 'elevated plus maze test', count: 2, isSelected: false }, + { value: 'memory behavior', count: 2, isSelected: false }, + { value: 'pharmacokinetics', count: 2, isSelected: false }, + { value: 'proximity extension assay', count: 2, isSelected: false }, + { value: 'wheel running', count: 2, isSelected: false }, + { value: 'anxiety-related behavior', count: 1, isSelected: false }, + { value: 'electrophysiology', count: 1, isSelected: false }, + { + value: 'elevated T maze apparatus method', + count: 1, + isSelected: false, + }, + { value: 'immunohistochemistry', count: 1, isSelected: false }, + { value: 'kinesthetic behavior', count: 1, isSelected: false }, + { + value: 'locomotor activation behavior', + count: 1, + isSelected: false, + }, + { value: 'questionnaire', count: 1, isSelected: false }, + { value: 'rotarod', count: 1, isSelected: false }, + { value: 'TMTquantitation', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'organ', + facetType: 'enumeration', + facetValues: [ + { value: 'brain', count: 134265, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 41253, + isSelected: false, + }, + { value: 'blood', count: 5924, isSelected: false }, + { value: 'large intestine', count: 1152, isSelected: false }, + { value: 'liver', count: 258, isSelected: false }, + { value: 'spinal cord', count: 24, isSelected: false }, + { value: 'skin', count: 22, isSelected: false }, + { value: 'blood, brain', count: 6, isSelected: false }, + { value: 'temporal cortex', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'tissue', + facetType: 'enumeration', + facetValues: [ + { + value: 'dorsolateral prefrontal cortex', + count: 63557, + isSelected: false, + }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 50108, + isSelected: false, + }, + { value: 'temporal cortex', count: 11595, isSelected: false }, + { value: 'superior temporal gyrus', count: 9805, isSelected: false }, + { value: 'prefrontal cortex', count: 5606, isSelected: false }, + { value: 'blood', count: 5566, isSelected: false }, + { + value: 'right cerebral hemisphere', + count: 5132, + isSelected: false, + }, + { value: 'parahippocampal gyrus', count: 4789, isSelected: false }, + { value: 'left cerebral hemisphere', count: 4274, isSelected: false }, + { value: 'caudate nucleus', count: 3028, isSelected: false }, + { value: 'cerebellum', count: 2949, isSelected: false }, + { value: 'middle temporal gyrus', count: 2393, isSelected: false }, + { value: 'frontal pole', count: 2229, isSelected: false }, + { value: 'inferior frontal gyrus', count: 2067, isSelected: false }, + { value: 'hippocampus', count: 1279, isSelected: false }, + { value: 'fecal material', count: 1152, isSelected: false }, + { value: 'cerebral cortex', count: 1114, isSelected: false }, + { value: 'frontal cortex', count: 1024, isSelected: false }, + { + value: 'posterior cingulate cortex', + count: 914, + isSelected: false, + }, + { value: 'head of caudate nucleus', count: 763, isSelected: false }, + { value: 'striatum', count: 689, isSelected: false }, + { value: 'anterior cingulate cortex', count: 510, isSelected: false }, + { value: 'mammillary body', count: 384, isSelected: false }, + { value: 'forebrain', count: 349, isSelected: false }, + { value: 'liver', count: 256, isSelected: false }, + { value: 'whole brain', count: 222, isSelected: false }, + { value: 'fusiform gyrus', count: 194, isSelected: false }, + { value: 'temporal pole', count: 157, isSelected: false }, + { value: 'primary visual cortex', count: 150, isSelected: false }, + { value: 'inferior temporal gyrus', count: 122, isSelected: false }, + { value: 'cerebellar cortex', count: 116, isSelected: false }, + { value: 'occipital visual cortex', count: 113, isSelected: false }, + { value: 'superior parietal lobe', count: 110, isSelected: false }, + { value: 'putamen', count: 108, isSelected: false }, + { value: 'precentral gyrus', count: 101, isSelected: false }, + { value: 'serum', count: 76, isSelected: false }, + { value: 'dentate gyrus', count: 65, isSelected: false }, + { value: 'amygdala', count: 54, isSelected: false }, + { value: 'nucleus accumbens', count: 54, isSelected: false }, + { value: 'precuneus', count: 51, isSelected: false }, + { value: 'middle frontal gyrus', count: 43, isSelected: false }, + { value: 'cortex', count: 34, isSelected: false }, + { value: 'temporal lobe', count: 26, isSelected: false }, + { value: 'plasma', count: 21, isSelected: false }, + { value: 'entorhinal cortex', count: 18, isSelected: false }, + { value: 'frontal lobe', count: 9, isSelected: false }, + { value: 'insula', count: 7, isSelected: false }, + { + value: 'plasma, cerebral cortex, cerebellum', + count: 6, + isSelected: false, + }, + { + value: ' dorsolateral prefrontal cortex', + count: 5, + isSelected: false, + }, + { value: 'midbrain', count: 4, isSelected: false }, + { value: 'parietal lobe', count: 2, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'species', + facetType: 'enumeration', + facetValues: [ + { value: 'Human', count: 140798, isSelected: false }, + { value: 'Mouse', count: 35330, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 5514, + isSelected: false, + }, + { value: 'Drosophila melanogaster', count: 1353, isSelected: false }, + { value: 'Rat', count: 34, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'sex', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 92431, + isSelected: false, + }, + { value: 'female', count: 52373, isSelected: false }, + { value: 'male', count: 38102, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'consortium', + facetType: 'enumeration', + facetValues: [ + { value: 'AMP-AD', count: 138145, isSelected: false }, + { value: 'CDCP', count: 18269, isSelected: false }, + { value: 'M2OVE-AD', count: 8259, isSelected: false }, + { value: 'cross-consortium', count: 6842, isSelected: false }, + { value: 'MODEL-AD', count: 5689, isSelected: false }, + { value: 'Resilience-AD', count: 4555, isSelected: false }, + { value: 'Psych-AD', count: 463, isSelected: false }, + { value: 'ELITE', count: 364, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 319, + isSelected: false, + }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'modelSystemName', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 175277, + isSelected: false, + }, + { value: '5XFAD', count: 2131, isSelected: false }, + { value: 'APOE4Trem2R47H', count: 865, isSelected: false }, + { value: 'hTauTrem2', count: 600, isSelected: false }, + { value: 'APOE-TR', count: 581, isSelected: false }, + { value: 'APPPS1', count: 490, isSelected: false }, + { value: 'Trem2R47H', count: 389, isSelected: false }, + { value: 'C57BL6J', count: 376, isSelected: false }, + { value: '3xTg-AD', count: 321, isSelected: false }, + { value: 'vascularInducible_apoE', count: 304, isSelected: false }, + { value: 'Trem2R47H_NSS', count: 250, isSelected: false }, + { value: 'APOE4KI', count: 229, isSelected: false }, + { value: 'APPPS1_miR155', count: 101, isSelected: false }, + { value: 'GJA1', count: 99, isSelected: false }, + { value: 'TyrobpKO', count: 96, isSelected: false }, + { value: 'hAbeta-loxP-KI', count: 93, isSelected: false }, + { value: 'TASTPM', count: 51, isSelected: false }, + { value: 'CRND8', count: 48, isSelected: false }, + { value: 'Abca7A1527GAPOE4Trem2R47H', count: 45, isSelected: false }, + { value: 'HDAC1-cKO', count: 43, isSelected: false }, + { value: 'Ceacam1KO', count: 39, isSelected: false }, + { value: 'hCR1KIAPOE4Trem2', count: 37, isSelected: false }, + { value: 'MthfrC677TSNP', count: 34, isSelected: false }, + { value: 'B6.Gfap-APOE4', count: 32, isSelected: false }, + { value: 'Kif21bT82TSNP', count: 32, isSelected: false }, + { value: 'APPPS1_Plexin-B1-KO', count: 31, isSelected: false }, + { value: 'APPS1', count: 31, isSelected: false }, + { value: 'Clasp2L163PSNP', count: 31, isSelected: false }, + { value: 'Il1rapKO', count: 31, isSelected: false }, + { value: 'Plexin-B1-KO', count: 31, isSelected: false }, + { value: 'Snx1D465NSNP', count: 31, isSelected: false }, + { value: 'ApoEKO', count: 30, isSelected: false }, + { value: 'B6.Clu', count: 30, isSelected: false }, + { value: 'Bin1.B6', count: 30, isSelected: false }, + { value: 'Cd2ap.B6', count: 30, isSelected: false }, + { value: 'Meox2KOHET', count: 28, isSelected: false }, + { value: 'Mtmr4V297GSNP', count: 28, isSelected: false }, + { value: 'TyrobpKO_APP/PS1', count: 25, isSelected: false }, + { value: 'PicalmH458R_rs117411388', count: 24, isSelected: false }, + { value: 'TgCRND8', count: 24, isSelected: false }, + { value: 'TauD35', count: 22, isSelected: false }, + { value: 'Trem2R47H_rs75932628', count: 18, isSelected: false }, + { + value: 'Abca7V1599M\u200b_rs117187003', + count: 16, + isSelected: false, + }, + { value: 'Bin1K358R_rs138047593', count: 16, isSelected: false }, + { value: 'Spi1_rs1377416', count: 16, isSelected: false }, + { value: 'TAU_Plexin-B1-KO', count: 16, isSelected: false }, + { value: 'TAUPS19', count: 16, isSelected: false }, + { value: 'Abi3S209F_rs616338', count: 14, isSelected: false }, + { value: 'AD-BXD', count: 9, isSelected: false }, + { value: 'AppKOAPOE4Trem2R47H', count: 3, isSelected: false }, + { value: 'hAPPAPOE4Trem2R47H', count: 2, isSelected: false }, + { value: 'Plcg2M28LSNP', count: 2, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'individualIdSource', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 167098, + isSelected: false, + }, + { + value: 'Mount Sinai NIH Brain and Tissue Repository', + count: 5154, + isSelected: false, + }, + { + value: "Rush Alzheimer's Disease Center", + count: 4736, + isSelected: false, + }, + { value: 'JAX', count: 2523, isSelected: false }, + { value: 'UCSF', count: 1970, isSelected: false }, + { value: 'UCI', count: 1111, isSelected: false }, + { value: 'UCI_TMF', count: 202, isSelected: false }, + { value: 'Mayo Clinic', count: 60, isSelected: false }, + { value: 'ROSMAP', count: 15, isSelected: false }, + { value: 'Mayo', count: 14, isSelected: false }, + { value: 'MSBB', count: 13, isSelected: false }, + { value: 'MayoBrainBaink', count: 3, isSelected: false }, + { value: 'MSSM', count: 3, isSelected: false }, + { value: 'Rush', count: 3, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'resourceType', + facetType: 'enumeration', + facetValues: [ + { value: 'experimentalData', count: 139038, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 29573, + isSelected: false, + }, + { value: 'analysis', count: 13770, isSelected: false }, + { value: 'metadata', count: 464, isSelected: false }, + { value: 'tool', count: 43, isSelected: false }, + { value: 'report', count: 15, isSelected: false }, + { value: ' tool', count: 2, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'dataSubtype', + facetType: 'enumeration', + facetValues: [ + { value: 'raw', count: 114030, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 43313, + isSelected: false, + }, + { value: 'processed', count: 18461, isSelected: false }, + { value: 'SNP variants', count: 4137, isSelected: false }, + { value: 'structural variants', count: 2364, isSelected: false }, + { value: 'normalized', count: 359, isSelected: false }, + { value: 'dataMatrix', count: 224, isSelected: false }, + { value: 'residualized', count: 13, isSelected: false }, + { value: 'metadata', count: 3, isSelected: false }, + { value: 'clinical', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'metadataType', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 182398, + isSelected: false, + }, + { value: 'assay', count: 192, isSelected: false }, + { value: 'biospecimen', count: 69, isSelected: false }, + { value: 'individual', count: 69, isSelected: false }, + { value: 'protocol', count: 67, isSelected: false }, + { value: 'data dictionary', count: 60, isSelected: false }, + { value: 'NA', count: 40, isSelected: false }, + { value: 'analytical covariates', count: 6, isSelected: false }, + { value: 'clinical', count: 2, isSelected: false }, + { value: 'ID mapping', count: 1, isSelected: false }, + { value: 'report', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'analysisType', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 163139, + isSelected: false, + }, + { + value: 'Supervised Machine Learning', + count: 6818, + isSelected: false, + }, + { value: 'quality control', count: 6011, isSelected: false }, + { value: 'Variant calling', count: 2585, isSelected: false }, + { value: 'genotype imputation', count: 1678, isSelected: false }, + { + value: 'search engine output files', + count: 1280, + isSelected: false, + }, + { + value: 'Mendelian Randomization Analysis', + count: 366, + isSelected: false, + }, + { value: 'differential expression', count: 326, isSelected: false }, + { value: 'Enrichment analysis', count: 243, isSelected: false }, + { + value: 'Gene expression comparison', + count: 161, + isSelected: false, + }, + { + value: 'statistical network reconstruction', + count: 56, + isSelected: false, + }, + { + value: 'chromatin accessible quantitative trait loci detection', + count: 44, + isSelected: false, + }, + { value: 'NA', count: 44, isSelected: false }, + { value: 'manifoldLearning', count: 36, isSelected: false }, + { value: 'network analysis', count: 33, isSelected: false }, + { + value: 'expression quantitative trait loci detection', + count: 17, + isSelected: false, + }, + { + value: ' statistical network reconstruction', + count: 16, + isSelected: false, + }, + { value: 'covariate specification', count: 12, isSelected: false }, + { value: 'data mining', count: 7, isSelected: false }, + { value: 'genome-wide pleiotropy', count: 7, isSelected: false }, + { value: 'Copy number estimation', count: 3, isSelected: false }, + { value: 'differentialExpression', count: 3, isSelected: false }, + { value: 'Polygenic Risk Scores', count: 3, isSelected: false }, + { + value: 'protein quantitative trait loci detection', + count: 3, + isSelected: false, + }, + { value: 'data normalization', count: 2, isSelected: false }, + { value: 'sequence alignment', count: 2, isSelected: false }, + { value: 'sequence annotation', count: 2, isSelected: false }, + { value: 'Clustering', count: 1, isSelected: false }, + { value: 'DNA methylation imputation', count: 1, isSelected: false }, + { value: 'EWAS', count: 1, isSelected: false }, + { value: 'Genome-Wide Association', count: 1, isSelected: false }, + { value: 'ion counting', count: 1, isSelected: false }, + { value: 'sequence annotation ', count: 1, isSelected: false }, + { + value: 'statisticalNetworkReconstruction', + count: 1, + isSelected: false, + }, + { value: 'transcript quantification', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'cellType', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 174254, + isSelected: false, + }, + { value: 'NeuN+', count: 2858, isSelected: false }, + { value: 'monocytes', count: 2287, isSelected: false }, + { value: 'NeuN-', count: 1056, isSelected: false }, + { value: 'microglia', count: 1002, isSelected: false }, + { value: 'neuron', count: 311, isSelected: false }, + { value: 'round', count: 304, isSelected: false }, + { value: 'immune cell', count: 165, isSelected: false }, + { value: 'NeuN-_sox10-', count: 162, isSelected: false }, + { value: 'NeuN-_sox10+', count: 154, isSelected: false }, + { value: 'lymphoblastoid cell line', count: 120, isSelected: false }, + { value: 'iPSC', count: 40, isSelected: false }, + { value: 'iPSC derived neurons', count: 32, isSelected: false }, + { + value: 'iPSC derived neuronal progenitor cells', + count: 31, + isSelected: false, + }, + { value: 'SH-SY5Y', count: 31, isSelected: false }, + { value: 'neurons', count: 27, isSelected: false }, + { + value: 'peripheral blood mononuclear cell', + count: 27, + isSelected: false, + }, + { value: 'macrophages', count: 17, isSelected: false }, + { value: 'astrocytes', count: 13, isSelected: false }, + { value: 'iPSC derived astrocytes', count: 13, isSelected: false }, + { value: 'iPSC derived microglia', count: 12, isSelected: false }, + { value: 'monocyte derived microglia', count: 6, isSelected: false }, + { value: 'macrophage', count: 2, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'nucleicAcidSource', + facetType: 'enumeration', + facetValues: [ + { value: 'bulk cell', count: 101538, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 65735, + isSelected: false, + }, + { value: 'single nucleus', count: 8428, isSelected: false }, + { value: 'sorted cells', count: 3734, isSelected: false }, + { value: 'single cell', count: 2487, isSelected: false }, + { value: 'sorted nuclei', count: 982, isSelected: false }, + { value: 'nucleus', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'fileFormat', + facetType: 'enumeration', + facetValues: [ + { value: 'fastq', count: 70687, isSelected: false }, + { value: 'bam', count: 17753, isSelected: false }, + { value: 'txt', count: 13714, isSelected: false }, + { value: 'jpg', count: 12170, isSelected: false }, + { value: 'tsv', count: 8671, isSelected: false }, + { value: 'raw', count: 7044, isSelected: false }, + { value: 'RData', count: 6822, isSelected: false }, + { value: 'xml', count: 6818, isSelected: false }, + { value: 'vcf', count: 4854, isSelected: false }, + { value: 'bai', count: 4727, isSelected: false }, + { value: 'idat', count: 4006, isSelected: false }, + { value: 'csv', count: 3397, isSelected: false }, + { value: 'tbi', count: 3078, isSelected: false }, + { value: 'zip', count: 2435, isSelected: false }, + { value: 'html', count: 2372, isSelected: false }, + { value: 'tar', count: 2214, isSelected: false }, + { value: 'abf', count: 2109, isSelected: false }, + { value: 'cel', count: 2107, isSelected: false }, + { value: 'pdf', count: 1801, isSelected: false }, + { value: 'dat', count: 1787, isSelected: false }, + { value: 'pepXML', count: 914, isSelected: false }, + { value: 'crai', count: 591, isSelected: false }, + { value: 'cram', count: 591, isSelected: false }, + { value: 'czi', count: 407, isSelected: false }, + { value: 'hdf', count: 325, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 321, + isSelected: false, + }, + { value: 'mp4', count: 273, isSelected: false }, + { value: 'mtx', count: 225, isSelected: false }, + { value: 'gzip', count: 157, isSelected: false }, + { value: 'cfg', count: 66, isSelected: false }, + { value: 'excel', count: 65, isSelected: false }, + { value: 'Sentrix descriptor file', count: 65, isSelected: false }, + { value: 'pdview', count: 53, isSelected: false }, + { value: 'R script', count: 48, isSelected: false }, + { value: 'avi', count: 36, isSelected: false }, + { value: 'plink', count: 31, isSelected: false }, + { value: 'pdresult', count: 27, isSelected: false }, + { value: 'msf', count: 26, isSelected: false }, + { value: 'RCC', count: 24, isSelected: false }, + { value: 'fasta', count: 15, isSelected: false }, + { value: 'doc', count: 11, isSelected: false }, + { value: 'bash script', count: 9, isSelected: false }, + { value: 'png', count: 9, isSelected: false }, + { value: 'xlsx', count: 5, isSelected: false }, + { value: 'bed', count: 4, isSelected: false }, + { value: 'db', count: 4, isSelected: false }, + { value: 'docx', count: 3, isSelected: false }, + { value: 'talon', count: 3, isSelected: false }, + { value: 'yaml', count: 3, isSelected: false }, + { value: 'bigwig', count: 2, isSelected: false }, + { value: 'bim', count: 2, isSelected: false }, + { value: 'bpm', count: 2, isSelected: false }, + { value: 'config', count: 2, isSelected: false }, + { value: 'fam', count: 2, isSelected: false }, + { value: 'gz', count: 2, isSelected: false }, + { value: 'locs', count: 2, isSelected: false }, + { value: 'pdstudy', count: 2, isSelected: false }, + { value: 'powerpoint', count: 2, isSelected: false }, + { value: 'bsc', count: 1, isSelected: false }, + { value: 'cov', count: 1, isSelected: false }, + { value: 'csv ', count: 1, isSelected: false }, + { value: 'gct', count: 1, isSelected: false }, + { value: 'hyperlink', count: 1, isSelected: false }, + { value: 'Python script', count: 1, isSelected: false }, + { value: 'R', count: 1, isSelected: false }, + { value: 'RLF', count: 1, isSelected: false }, + { value: 'Rscript', count: 1, isSelected: false }, + { value: 'saf', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'isModelSystem', + facetType: 'enumeration', + facetValues: [ + { value: 'false', count: 114193, isSelected: false }, + { value: 'true', count: 35513, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 33199, + isSelected: false, + }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'isMultiSpecimen', + facetType: 'enumeration', + facetValues: [ + { value: 'false', count: 107625, isSelected: false }, + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 56521, + isSelected: false, + }, + { value: 'true', count: 18759, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'metaboliteType', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 182845, + isSelected: false, + }, + { value: 'amino acids', count: 53, isSelected: false }, + { value: 'acylcarnitines', count: 47, isSelected: false }, + { value: 'sphingolipids', count: 47, isSelected: false }, + { value: 'biogenic amines', count: 44, isSelected: false }, + { value: 'glycerophospholipids', count: 44, isSelected: false }, + { value: 'fatty acids', count: 10, isSelected: false }, + { value: 'lipids', count: 4, isSelected: false }, + { value: 'bile acids', count: 3, isSelected: false }, + { value: 'carbohydrates', count: 3, isSelected: false }, + { value: 'cholesterol', count: 3, isSelected: false }, + { value: 'cofactors and vitamins', count: 3, isSelected: false }, + { value: 'glycerides', count: 3, isSelected: false }, + { value: 'ketones', count: 3, isSelected: false }, + { value: 'lipoproteins', count: 3, isSelected: false }, + { value: 'nucleotides', count: 3, isSelected: false }, + { value: 'peptides', count: 3, isSelected: false }, + { value: 'phospholipids', count: 3, isSelected: false }, + { value: 'xenobiotics', count: 3, isSelected: false }, + { value: 'lysophosphatidylcholine', count: 2, isSelected: false }, + { value: 'phosphatidylcholine', count: 2, isSelected: false }, + { value: ' acylcarnitines', count: 1, isSelected: false }, + { value: ' acylglycerols', count: 1, isSelected: false }, + { value: ' bile acids', count: 1, isSelected: false }, + { value: ' cholesterol', count: 1, isSelected: false }, + { value: ' fatty acids', count: 1, isSelected: false }, + { value: ' lysophosphatidylcholine', count: 1, isSelected: false }, + { value: ' phosphatidylcholine', count: 1, isSelected: false }, + { value: ' phosphatidylethanolamine', count: 1, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'chromosome', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 176726, + isSelected: false, + }, + { value: 'mitochondrial', count: 5652, isSelected: false }, + { value: '', count: 439, isSelected: false }, + { value: '1', count: 4, isSelected: false }, + { value: '10', count: 4, isSelected: false }, + { value: '11', count: 4, isSelected: false }, + { value: '12', count: 4, isSelected: false }, + { value: '13', count: 4, isSelected: false }, + { value: '14', count: 4, isSelected: false }, + { value: '15', count: 4, isSelected: false }, + { value: '16', count: 4, isSelected: false }, + { value: '17', count: 4, isSelected: false }, + { value: '18', count: 4, isSelected: false }, + { value: '19', count: 4, isSelected: false }, + { value: '2', count: 4, isSelected: false }, + { value: '20', count: 4, isSelected: false }, + { value: '21', count: 4, isSelected: false }, + { value: '22', count: 4, isSelected: false }, + { value: '3', count: 4, isSelected: false }, + { value: '4', count: 4, isSelected: false }, + { value: '5', count: 4, isSelected: false }, + { value: '6', count: 4, isSelected: false }, + { value: '7', count: 4, isSelected: false }, + { value: '8', count: 4, isSelected: false }, + { value: '9', count: 4, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'modelSystemType', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 146406, + isSelected: false, + }, + { value: 'animal', count: 35971, isSelected: false }, + { value: 'iPSC', count: 249, isSelected: false }, + { value: 'immortalized cell line', count: 161, isSelected: false }, + { value: 'cerebral organoid', count: 102, isSelected: false }, + { value: '5XFAD', count: 16, isSelected: false }, + ], + }, + { + concreteType: + 'org.sagebionetworks.repo.model.table.FacetColumnResultValues', + columnName: 'libraryPrep', + facetType: 'enumeration', + facetValues: [ + { + value: 'org.sagebionetworks.UNDEFINED_NULL_NOTSET', + count: 144741, + isSelected: false, + }, + { value: 'rRNAdepletion', count: 18442, isSelected: false }, + { value: 'polyAselection', count: 17251, isSelected: false }, + { value: 'amplicon', count: 1155, isSelected: false }, + { value: 'multiome', count: 364, isSelected: false }, + { value: '10x', count: 316, isSelected: false }, + { value: 'totalRNA', count: 300, isSelected: false }, + { value: 'ddSEQ', count: 155, isSelected: false }, + { value: 'cellHashing', count: 139, isSelected: false }, + { value: 'proximity_ligation', count: 38, isSelected: false }, + { value: 'NA', count: 4, isSelected: false }, + ], + }, + ], + sumFileSizes: { sumFileSizesBytes: 40649548915, greaterThan: true }, + lastUpdatedOn: '2023-06-27T05:49:31.831Z', +} + +export const mockCompleteAsyncJob: AsynchronousJobStatus< + QueryBundleRequest, + QueryResultBundle +> = { + jobState: 'COMPLETE', + jobCanceling: false, + requestBody: mockQueryBundleRequest, + responseBody: mockQueryResultBundle, + etag: 'd7773e3a-982b-4a75-a695-3d56874e6ff1', + jobId: '28927578', + startedByUserId: 273950, + startedOn: '2023-06-27T16:25:36.684Z', + changedOn: '2023-06-27T16:25:55.274Z', + progressMessage: 'Complete', + progressCurrent: 100, + progressTotal: 100, + runtimeMS: 18590, +} diff --git a/packages/synapse-react-client/package.json b/packages/synapse-react-client/package.json index de88f714d3..04fe198218 100644 --- a/packages/synapse-react-client/package.json +++ b/packages/synapse-react-client/package.json @@ -220,6 +220,7 @@ "react-test-renderer": "^18.2.0", "rollup-plugin-polyfill-node": "^0.10.2", "storybook": "^7.0.23", + "storybook-addon-designs": "7.0.0-beta.2", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "timers-browserify": "^2.0.12", diff --git a/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx index 48a5595042..3171939231 100644 --- a/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx +++ b/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -15,6 +15,14 @@ export type ConfirmationButtonsProps = { export const CANCEL_BUTTON_TEXT = 'Cancel' +export function CancelButton(props: { onCancel: () => void }) { + return ( + + ) +} + export const ConfirmationButtons = (props: ConfirmationButtonsProps) => { const { confirmButtonText = 'OK', @@ -28,11 +36,7 @@ export const ConfirmationButtons = (props: ConfirmationButtonsProps) => { } = props return ( <> - {hasCancelButton && ( - - )} + {hasCancelButton && } + ) + })} + {showExportToCavatica && ( + + )} + + } + /> + ) +} diff --git a/packages/synapse-react-client/src/components/SynapseTable/RowSelection/RowSelectionUI.tsx b/packages/synapse-react-client/src/components/SynapseTable/RowSelection/RowSelectionUI.tsx new file mode 100644 index 0000000000..ca830dc74a --- /dev/null +++ b/packages/synapse-react-client/src/components/SynapseTable/RowSelection/RowSelectionUI.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import { Badge, Box, Button, Paper, Typography } from '@mui/material' + +export type RowSelectionUIProps = { + show?: boolean + selectedRowCount: number + onClearSelection: () => void + customControls?: React.ReactNode +} + +/** + * UI-only component for displaying the number of table rows selected, along with actions that can be performed on those rows + */ +export function RowSelectionUI(props: RowSelectionUIProps) { + const { + show = true, + selectedRowCount, + onClearSelection, + customControls = <>, + } = props + if (!show) { + return <> + } + + return ( + + + + {' '} + Rows Selected + +
{customControls}
+
+ ) +} diff --git a/packages/synapse-react-client/src/components/SynapseTable/RowSelectionControls.tsx b/packages/synapse-react-client/src/components/SynapseTable/RowSelectionControls.tsx deleted file mode 100644 index 16bfa584f9..0000000000 --- a/packages/synapse-react-client/src/components/SynapseTable/RowSelectionControls.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react' -import { CustomControl } from './TopLevelControls' -import { QueryResultBundle, Row } from '@sage-bionetworks/synapse-types' -import { Badge, Box, Button, Paper, Typography } from '@mui/material' - -export type RowSelectionControlsProps = { - customControls: CustomControl[] - data?: QueryResultBundle - selectedRows: Row[] - setSelectedRows: (newState: Row[]) => void - refresh: () => void -} - -export const RowSelectionControls: React.FunctionComponent< - RowSelectionControlsProps -> = ({ customControls, data, selectedRows, setSelectedRows, refresh }) => { - if (selectedRows.length == 0) { - return <> - } - return ( - - - - {' '} - Rows Selected - -
- {customControls.map(customControl => { - return ( - - ) - })} -
-
- ) -} diff --git a/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx b/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx index 5b47151d46..46c10b874d 100644 --- a/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx +++ b/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx @@ -1,33 +1,71 @@ -import React from 'react' -import ConfirmationDialog from '../ConfirmationDialog' -import { Box } from '@mui/material' -import { Typography } from '@mui/material' -import { Link } from '@mui/material' +import React, { useMemo } from 'react' +import { ConfirmationDialog } from '../ConfirmationDialog' +import { Box, Link, Typography } from '@mui/material' import { ActionRequiredListItem } from '../DownloadCart/ActionRequiredListItem' -import { ActionRequiredCount } from '@sage-bionetworks/synapse-types' +import { + ActionRequiredCount, + ColumnSingleValueFilterOperator, + ColumnSingleValueQueryFilter, +} from '@sage-bionetworks/synapse-types' import { useQueryContext } from '../QueryContext' import { SynapseConstants } from '../../utils' import { useGetQueryResultBundleWithAsyncStatus } from '../../synapse-queries' import { SkeletonParagraph } from '../Skeleton' import { useExportToCavatica } from '../../synapse-queries/entity/useExportToCavatica' +import { useQueryVisualizationContext } from '../QueryVisualizationWrapper' +import { cloneDeep } from 'lodash-es' +import pluralize from 'pluralize' export type SendToCavaticaConfirmationDialogProps = { - showing: boolean cavaticaHelpURL?: string - onHide: () => void } export default function SendToCavaticaConfirmationDialog( props: SendToCavaticaConfirmationDialogProps, ) { - const { cavaticaHelpURL, onHide, showing } = props + const { cavaticaHelpURL } = props const { data, getLastQueryRequest, onViewSharingSettingsClicked } = useQueryContext() + const { + isShowingExportToCavaticaModal, + setIsShowingExportToCavaticaModal, + isRowSelectionVisible, + selectedRows, + unitDescription, + } = useQueryVisualizationContext() + + const hasSelectedRows = isRowSelectionVisible && selectedRows.length > 0 + + const cavaticaQueryRequest = useMemo(() => { + const request = getLastQueryRequest() + if (!hasSelectedRows) { + return request + } else { + // Add a filter that will just return the selected rows. + const idColIndex = data?.columnModels?.findIndex(cm => cm.name === 'id') + const idColumnFilter: ColumnSingleValueQueryFilter = { + concreteType: + 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter', + columnName: 'id', + operator: ColumnSingleValueFilterOperator.IN, + values: selectedRows!.map(row => row.values[idColIndex!]!), + } + request.query.additionalFilters = [ + ...(request.query.additionalFilters || []), + idColumnFilter, + ] + return request + } + }, [data?.columnModels, getLastQueryRequest, selectedRows, hasSelectedRows]) + const exportToCavatica = useExportToCavatica( - getLastQueryRequest(), + cavaticaQueryRequest, data?.queryResult?.queryResults.headers, ) - const queryRequestCopy = getLastQueryRequest() + const queryRequestCopy = useMemo( + () => cloneDeep(cavaticaQueryRequest), + [cavaticaQueryRequest], + ) queryRequestCopy.partMask = SynapseConstants.BUNDLE_MASK_ACTIONS_REQUIRED const { data: asyncJobStatus, isLoading } = useGetQueryResultBundleWithAsyncStatus(queryRequestCopy, { @@ -37,9 +75,22 @@ export default function SendToCavaticaConfirmationDialog( const queryResultBundle = asyncJobStatus?.responseBody const actions: ActionRequiredCount[] | undefined = queryResultBundle?.actionsRequired + + let confirmButtonText + if (!hasSelectedRows) { + confirmButtonText = `Send all ${data?.queryCount?.toLocaleString()} ${pluralize( + unitDescription, + )} to CAVATICA` + } else { + confirmButtonText = `Send ${selectedRows.length.toLocaleString()} selected ${pluralize( + unitDescription, + selectedRows.length, + )} to CAVATICA` + } + return ( @@ -153,12 +204,12 @@ export default function SendToCavaticaConfirmationDialog( } - confirmButtonText="Send to CAVATICA" + confirmButtonText={confirmButtonText} confirmButtonDisabled={isLoading || (actions && actions.length > 0)} onConfirm={() => { exportToCavatica() }} - onCancel={onHide} + onCancel={() => setIsShowingExportToCavaticaModal(false)} maxWidth="md" /> ) diff --git a/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls.tsx b/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls.tsx index 0a4c8ae0a3..cbec9fcab8 100644 --- a/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls.tsx +++ b/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls.tsx @@ -19,7 +19,7 @@ import QueryCount from '../QueryCount/QueryCount' import { Icon } from '../row_renderers/utils' import MissingQueryResultsWarning from '../MissingQueryResultsWarning' import { Cavatica } from '../../assets/icons/Cavatica' -import { RowSelectionControls } from './RowSelectionControls' +import { RowSelectionControls } from './RowSelection/RowSelectionControls' import SendToCavaticaConfirmationDialog from './SendToCavaticaConfirmationDialog' export type TopLevelControlsProps = { @@ -51,7 +51,7 @@ export type CustomControl = { buttonText: string onClick: (event: CustomControlCallbackData) => void classNames?: string - icon?: string + icon?: React.ReactNode } const controls: Control[] = [ @@ -96,22 +96,16 @@ const TopLevelControls = (props: TopLevelControlsProps) => { cavaticaHelpURL, } = props - const { - data, - entity, - executeQueryRequest, - getLastQueryRequest, - getInitQueryRequest, - lockedColumn, - } = useQueryContext() + const { data, entity, getInitQueryRequest, lockedColumn } = useQueryContext() const { topLevelControlsState, setTopLevelControlsState, columnsToShowInTable, + isRowSelectionVisible, selectedRows, - setSelectedRows, setColumnsToShowInTable, + setIsShowingExportToCavaticaModal, } = useQueryVisualizationContext() const { showCopyToClipboard } = topLevelControlsState @@ -135,15 +129,6 @@ const TopLevelControls = (props: TopLevelControlsProps) => { })) } - const [isShowingExportToCavaticaModal, setIsShowingExportToCavaticaModal] = - useState(false) - const refresh = () => { - // clear selection - setSelectedRows([]) - // refresh the data - executeQueryRequest(getLastQueryRequest()) - } - /** * We show the total number of results that would be shown if the user removed their filters. * To do this, we have to create a query that captures those results. @@ -183,6 +168,7 @@ const TopLevelControls = (props: TopLevelControlsProps) => { setColumnsToShowInTable(columnsToShowInTableCopy) } const showFacetFilter = topLevelControlsState?.showFacetFilter + const hasSelectedRows = isRowSelectionVisible && selectedRows.length > 0 return (
{
{showExportToCavatica && ( + This action will send a reference to{' '} + {hasSelectedRows + ? 'each selected file' + : 'every file in the current table'}{' '} + to CAVATICA.{' '} + {!hasSelectedRows && + topLevelControlsState.showFacetFilter && ( + <> + You can change what is sent by applying filters using + the controls in the sidebar. + + )} + {hasSelectedRows && ( + <> + You can change what is sent by selecting a different set + of files. + + )} + + } >
diff --git a/packages/synapse-react-client/src/components/widgets/ElementWithTooltip.tsx b/packages/synapse-react-client/src/components/widgets/ElementWithTooltip.tsx index 455a79d56e..2801b37b71 100644 --- a/packages/synapse-react-client/src/components/widgets/ElementWithTooltip.tsx +++ b/packages/synapse-react-client/src/components/widgets/ElementWithTooltip.tsx @@ -34,7 +34,7 @@ type ElementWithTooltipProps = React.PropsWithChildren<{ tooltipVisualProps?: Partial darkTheme?: boolean size?: string - icon?: string + icon?: React.ReactNode }> function getTooltipTriggerContents( @@ -62,7 +62,13 @@ export const ElementWithTooltip = ({ icon, }: ElementWithTooltipProps) => { const { place } = tooltipVisualProps - const iconComponent = icon ? : undefined + const iconComponent = icon ? ( + typeof icon === 'string' ? ( + + ) : ( + icon + ) + ) : undefined const tooltipTriggerContents = iconComponent ? iconComponent : image diff --git a/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts b/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts index 10160352bc..3b7f554fbf 100644 --- a/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts +++ b/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts @@ -186,6 +186,7 @@ export const FileViewWithLockedColumn: Story = { export const SendToCavatica: Story = { args: { sql: 'SELECT * FROM syn51186974', + isRowSelectionVisible: true, tableConfiguration: { showAccessColumn: true, showDownloadColumn: true, diff --git a/packages/synapse-react-client/stories/RowSelection.stories.tsx b/packages/synapse-react-client/stories/RowSelection.stories.tsx new file mode 100644 index 0000000000..0e0c6dc268 --- /dev/null +++ b/packages/synapse-react-client/stories/RowSelection.stories.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { Meta, StoryObj } from '@storybook/react' +import { displayToast } from '../src' +import { GetApp } from '@mui/icons-material' +import { RowSelectionUI } from '../src/components/SynapseTable/RowSelection/RowSelectionUI' +import { Button } from '@mui/material' + +const meta = { + title: 'Explore/RowSelection', + component: RowSelectionUI, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Demo: Story = { + name: 'RowSelection', + args: { + show: true, + customControls: ( + <> + + + + ), + selectedRowCount: 5, + }, +} diff --git a/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx b/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx new file mode 100644 index 0000000000..91972bdfcc --- /dev/null +++ b/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { Meta, StoryObj } from '@storybook/react' +import { QueryContextProvider } from '../src/components/QueryContext' +import { QueryVisualizationContextProvider } from '../src/components/QueryVisualizationWrapper' +import { + mockQueryBundleRequest, + mockQueryResultBundle, +} from '../mocks/mockFileViewQuery' +import { deepClone } from '@mui/x-data-grid/utils/utils' +import { displayToast } from '../src/components/ToastMessage' +import SendToCavaticaConfirmationDialog from '../src/components/SynapseTable/SendToCavaticaConfirmationDialog' + +const meta = { + title: 'Explore/Send to CAVATICA Dialog', + component: SendToCavaticaConfirmationDialog, + argTypes: { + hasRowSelection: { + description: + 'Simulates whether or not the table has row selection enabled.', + control: { type: 'boolean' }, + }, + unitDescription: { + description: 'A word describing what each row represents', + control: { type: 'text' }, + }, + }, +} satisfies Meta +export default meta +type Story = StoryObj + +export const Demo: Story = { + name: 'Send to CAVATICA Dialog', + args: { + hasRowSelection: false, + unitDescription: 'file', + }, + decorators: [ + (Story, { args }) => { + return ( + deepClone(mockQueryBundleRequest), + }} + > + { + displayToast('close modal called') + }, + isRowSelectionVisible: args.hasRowSelection, + selectedRows: args.hasRowSelection + ? mockQueryResultBundle.queryResult!.queryResults.rows.slice( + 0, + 2, + ) + : [], + unitDescription: args.unitDescription, + }} + > + + + + ) + }, + ], +} diff --git a/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx b/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx new file mode 100644 index 0000000000..c2ebcf3f83 --- /dev/null +++ b/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx @@ -0,0 +1,132 @@ +import React from 'react' +import SendToCavaticaConfirmationDialog from '../../../src/components/SynapseTable/SendToCavaticaConfirmationDialog' +import { + displayToast, + QueryContextProvider, + QueryContextType, + SynapseContextType, +} from '../../../src' +import { render, screen } from '@testing-library/react' +import { createWrapper } from '../../testutils/TestingLibraryUtils' +import userEvent from '@testing-library/user-event' +import { + mockQueryBundleRequest, + mockQueryResultBundle, +} from '../../../mocks/mockFileViewQuery' +import { + QueryVisualizationContextProvider, + QueryVisualizationContextType, +} from '../../../src/components/QueryVisualizationWrapper' +import { ColumnSingleValueFilterOperator } from '@sage-bionetworks/synapse-types' +import { cloneDeep } from 'lodash-es' +import * as UseExportToCavaticaModule from '../../../src/synapse-queries/entity/useExportToCavatica' + +const onExportToCavatica = jest.fn() + +const mockUseExportToCavatica = jest + .spyOn(UseExportToCavaticaModule, 'useExportToCavatica') + .mockImplementation(() => { + return onExportToCavatica + }) + +function renderComponent( + wrapperProps?: SynapseContextType, + queryContextOverrides?: Partial, + queryVisualizationContextOverrides?: Partial, +) { + return render( + cloneDeep(mockQueryBundleRequest), + ...queryContextOverrides, + }} + > + { + displayToast('close modal called') + }, + isRowSelectionVisible: true, + selectedRows: [], + resultsToExportToCavatica: 'ALL', + unitDescription: 'result', + ...queryVisualizationContextOverrides, + }} + > + + + , + { + wrapper: createWrapper(wrapperProps), + }, + ) +} + +function setUp( + wrapperProps?: SynapseContextType, + queryContextOverrides?: Partial, + queryVisualizationContextOverrides?: Partial, +) { + const user = userEvent.setup() + const component = renderComponent( + wrapperProps, + queryContextOverrides, + queryVisualizationContextOverrides, + ) + const sendToCavatica = screen.getByRole('button', { name: /CAVATICA/i }) + return { component, user, sendToCavatica } +} + +describe('Export to CAVATICA Modal', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + it('allows exporting all results', async () => { + const { user, sendToCavatica } = setUp(undefined, undefined, { + isRowSelectionVisible: false, + selectedRows: [], + }) + + await user.click(sendToCavatica) + + expect(mockUseExportToCavatica).toHaveBeenCalledWith( + mockQueryBundleRequest, + mockQueryResultBundle.queryResult.queryResults.headers, + ) + }) + + it('allows exporting selected results', async () => { + const { user, sendToCavatica } = setUp(undefined, undefined, { + isRowSelectionVisible: true, + selectedRows: mockQueryResultBundle.queryResult.queryResults.rows.slice( + 0, + 2, + ), + }) + + await user.click(sendToCavatica) + + expect(mockUseExportToCavatica).toHaveBeenCalledWith( + expect.objectContaining({ + ...mockQueryBundleRequest, + query: { + ...mockQueryBundleRequest.query, + additionalFilters: [ + { + concreteType: + 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter', + columnName: 'id', + operator: ColumnSingleValueFilterOperator.IN, + values: mockQueryResultBundle.queryResult.queryResults.rows + .slice(0, 2) + .map(row => row.values[0]), + }, + ], + }, + }), + mockQueryResultBundle.queryResult.queryResults.headers, + ) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0eda2aa43e..cd5021aa2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1281,6 +1281,9 @@ importers: storybook: specifier: ^7.0.23 version: 7.0.23 + storybook-addon-designs: + specifier: 7.0.0-beta.2 + version: 7.0.0-beta.2(@storybook/addon-docs@7.0.23)(@storybook/addons@7.0.24)(@storybook/api@7.0.23)(@storybook/components@7.0.23)(@storybook/theming@7.0.23)(react-dom@18.2.0)(react@18.2.0) stream-browserify: specifier: ^3.0.0 version: 3.0.0 @@ -4418,6 +4421,22 @@ packages: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true + /@figspec/components@1.0.1: + resolution: {integrity: sha512-UvnEamPEAMh9HExViqpobWmX25g1+soA9kcJu+It3VerMa7CeVyaIbQydNf1Gys5v/rxJVdTDRgQ7OXW2zAAig==} + dependencies: + lit: 2.7.5 + dev: true + + /@figspec/react@1.0.3(react@18.2.0): + resolution: {integrity: sha512-r683qOko+5CbT48Ox280fMx2MNAtaFPgCNJvldOqN3YtmAzlcTT+YSxd3OahA+kjXGGrnzDbUgeTOX1cPLII+g==} + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@figspec/components': 1.0.1 + '@lit-labs/react': 1.2.0 + react: 18.2.0 + dev: true + /@floating-ui/core@1.2.4: resolution: {integrity: sha512-SQOeVbMwb1di+mVWWJLpsUTToKfqVNioXys011beCAhyOIFtS+GQoW4EQSneuxzmQKddExDwQ+X0hLl4lJJaSQ==} @@ -4779,6 +4798,20 @@ packages: /@juggle/resize-observer@3.4.0: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + /@lit-labs/react@1.2.0: + resolution: {integrity: sha512-AIfHLsy4Uk0MSxZTVLrtmdkGnAgCOoAvBCAvTdOXsqp60Vb4zZTUpc0C3CJQ6e8FjM6JL0avOFFBo3XcfARq2Q==} + dev: true + + /@lit-labs/ssr-dom-shim@1.1.1: + resolution: {integrity: sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==} + dev: true + + /@lit/reactive-element@1.6.2: + resolution: {integrity: sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==} + dependencies: + '@lit-labs/ssr-dom-shim': 1.1.1 + dev: true + /@mapbox/geojson-rewind@0.5.2: resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} hasBin: true @@ -6033,6 +6066,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@storybook/addons@7.0.24(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-e15hORnOD0ugvOVOTyZyLJhbDTWa4G1OHVUlboazy8O4TSvAXNBdLV1wOdY5RGoGD6Z5A4iR/gZXM0qc6Fh9xg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/manager-api': 7.0.24(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.24 + '@storybook/types': 7.0.24 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/api@7.0.23(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-OgEYdwk2XpiP04V9U2PWeG6SPxeYSPZUwhtsUylMePff9icOTXNJwb87lFBDxPEdMPEYOVIPFHy30dAHTaOIyQ==} peerDependencies: @@ -6162,6 +6208,17 @@ packages: telejson: 7.0.4 dev: true + /@storybook/channel-postmessage@7.0.24: + resolution: {integrity: sha512-QLtLXjEeTEwBN/7pB888mBaykmRU9Jy2BitvZuLJWyHHygTYm3vYZOaGR37DT+q/6Ob5GaZ0tURZmCSNDe8IIA==} + dependencies: + '@storybook/channels': 7.0.24 + '@storybook/client-logger': 7.0.24 + '@storybook/core-events': 7.0.24 + '@storybook/global': 5.0.0 + qs: 6.11.0 + telejson: 7.0.4 + dev: true + /@storybook/channel-websocket@7.0.23: resolution: {integrity: sha512-xjY09pOaE5T5TgC41V3fezzqdrL+aPjiW0q4H/CrPF9Oa87hHBZq2dmq1TU5Wd4GFrW/OHqo2rGemS/bXh8mNg==} dependencies: @@ -6179,6 +6236,10 @@ packages: resolution: {integrity: sha512-cCxR3Z84YQjsVMPgFTI+kDVNOlgXSDakwjkNFBznU+s2qhGW5eZt2g9YRDeVDQ6AjR4j4RrGhwddRq4lQZF2pg==} dev: true + /@storybook/channels@7.0.24: + resolution: {integrity: sha512-NZVLwMhtzy6cZrNRjshFvMAD9mQTmJDNwhohodSkM/YFCDVFhmxQk9tgizVGh9MwY3CYGJ1SI96RUejGosb49Q==} + dev: true + /@storybook/cli@7.0.23: resolution: {integrity: sha512-6os+7rQN/Bx89bOgx/Ju+n0WXi2BN+eBIyvPJrZ7r5tl389lqL7IKHJFYmQ/FnIzhGvwuUxmoSq5niCt2Hvc3w==} hasBin: true @@ -6234,6 +6295,12 @@ packages: '@storybook/global': 5.0.0 dev: true + /@storybook/client-logger@7.0.24: + resolution: {integrity: sha512-4zRTb+QQ1hWaRqad/UufZNRfi2d/cf5a40My72Ct97VwjhJFE6aQ3K+hl1Xt6hh8dncDL2JK3cgziw6ElqjT0w==} + dependencies: + '@storybook/global': 5.0.0 + dev: true + /@storybook/codemod@7.0.23: resolution: {integrity: sha512-Jr1UmOT4h/0Cst1a6xOIxCstN7arJYdQPvcmnM9QUqYjVpJ65y8ASANinyD27xZS8pshJ38z4pPzZCFE+YVP3Q==} dependencies: @@ -6312,6 +6379,10 @@ packages: resolution: {integrity: sha512-Hdt18p/qbgJc+1wY2dGcdjmlsuNXWsoLTaXrjInuvr1U0kmKmKs0VMaB0cFubnUgCmB3YQWTGnVr3q8iz9iB7g==} dev: true + /@storybook/core-events@7.0.24: + resolution: {integrity: sha512-xkf/rihCkhqMeh5EA8lVp90/mzbb2gcg6I3oeFWw2hognVcTnPXg6llhWdU4Spqd0cals7GEFmQugIILCmH8GA==} + dev: true + /@storybook/core-server@7.0.23: resolution: {integrity: sha512-xHt2WB2kL7VQIxYgtE1TDjd4WEvyqlaf256L3RbuQVGZ/AkuFUEV60FULimM6V+/DyF83hGZTREkjovI+Mb16w==} dependencies: @@ -6474,6 +6545,31 @@ packages: ts-dedent: 2.2.0 dev: true + /@storybook/manager-api@7.0.24(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cBpgDWq8reFgyrv4fBZlZJQyWYb9cDW0LDe476rWn/29uXNvYMNsHRwveLNgSA8Oy1NdyQCgf4ZgcYvY3wpvMA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/channels': 7.0.24 + '@storybook/client-logger': 7.0.24 + '@storybook/core-events': 7.0.24 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/router': 7.0.24(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.24(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.24 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + semver: 7.3.8 + store2: 2.14.2 + telejson: 7.0.4 + ts-dedent: 2.2.0 + dev: true + /@storybook/manager@7.0.23: resolution: {integrity: sha512-D3WIqtzjSY3UOskZhKQ2R7RypPUeqAmsXLKxw2EEEx7iLHgJfKvFeAZ77NCKNOxQsEDjLrjTQH4WjiKEaSpK5Q==} dev: true @@ -6515,6 +6611,26 @@ packages: util-deprecate: 1.0.2 dev: true + /@storybook/preview-api@7.0.24: + resolution: {integrity: sha512-psycU07tuB5nyJvfAJiDN/9e8cjOdJ+5lrCSYC3vPzH86LxADDIN0/8xFb1CaQWcXZsADEFJGpHKWbRhjym5ew==} + dependencies: + '@storybook/channel-postmessage': 7.0.24 + '@storybook/channels': 7.0.24 + '@storybook/client-logger': 7.0.24 + '@storybook/core-events': 7.0.24 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/types': 7.0.24 + '@types/qs': 6.9.7 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + qs: 6.11.0 + synchronous-promise: 2.0.17 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 + dev: true + /@storybook/preview@7.0.23: resolution: {integrity: sha512-D4oDayFOXqNDLJStbZ35Lc0UAXvzdWiij1IE01wH1mzndlEgR+/1ZEPQfm5Leb5LZd7pWmyYLJqh6m/CCK2uPg==} dev: true @@ -6609,6 +6725,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@storybook/router@7.0.24(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-SRCV+srCZUbko/V0phVN8jY8ilrxQWWAY/gegwNlIYaNqLJSyYqIj739VDmX+deXl6rOEpFLZreClVXWiDU9+w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/client-logger': 7.0.24 + memoizerific: 1.11.3 + qs: 6.11.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/telemetry@7.0.23: resolution: {integrity: sha512-bV6U58+JXvliq6FHnEOmy902Coa2JVD0M1N6En0us9kNNrtxpn4xSO4dvFW0A+veZimtT6kI55liG89IKeN3Nw==} dependencies: @@ -6648,6 +6777,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@storybook/theming@7.0.24(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-CMeCCfqffJ/D5rBl1HpAM/e5Vw0h7ucT+CLzP0ALtLrguz9ZzOiIZYgMj17KpfvWqje7HT+DwEtNkSrnJ01FNQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) + '@storybook/client-logger': 7.0.24 + '@storybook/global': 5.0.0 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/types@7.0.17: resolution: {integrity: sha512-orjuWtGIIW/S+dvEtA+aqByh4tTHpsNGqJMGcI0Ec6wW4Wv7s62pFvIuU0lWC/fL6Hot/bTY7tSeqrQrTLplog==} dependencies: @@ -6666,6 +6809,15 @@ packages: file-system-cache: 2.0.2 dev: true + /@storybook/types@7.0.24: + resolution: {integrity: sha512-SZh/XBHP1TT5bmEk0W52nT0v6fUnYwmZVls3da5noutdgOAiwL7TANtl41XrNjG+UDr8x0OE3PVVJi+LhwUaNA==} + dependencies: + '@storybook/channels': 7.0.24 + '@types/babel__core': 7.20.0 + '@types/express': 4.17.16 + file-system-cache: 2.3.0 + dev: true + /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.21.5): resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -7694,6 +7846,10 @@ packages: resolution: {integrity: sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==} dev: true + /@types/trusted-types@2.0.3: + resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} + dev: true + /@types/ua-parser-js@0.7.36: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: false @@ -11641,6 +11797,13 @@ packages: ramda: 0.28.0 dev: true + /file-system-cache@2.3.0: + resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} + dependencies: + fs-extra: 11.1.1 + ramda: 0.29.0 + dev: true + /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -14514,6 +14677,28 @@ packages: wrap-ansi: 7.0.0 dev: true + /lit-element@3.3.2: + resolution: {integrity: sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==} + dependencies: + '@lit-labs/ssr-dom-shim': 1.1.1 + '@lit/reactive-element': 1.6.2 + lit-html: 2.7.4 + dev: true + + /lit-html@2.7.4: + resolution: {integrity: sha512-/Jw+FBpeEN+z8X6PJva5n7+0MzCVAH2yypN99qHYYkq8bI+j7I39GH+68Z/MZD6rGKDK9RpzBw7CocfmHfq6+g==} + dependencies: + '@types/trusted-types': 2.0.3 + dev: true + + /lit@2.7.5: + resolution: {integrity: sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ==} + dependencies: + '@lit/reactive-element': 1.6.2 + lit-element: 3.3.2 + lit-html: 2.7.4 + dev: true + /load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -16787,6 +16972,10 @@ packages: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: true + /ramda@0.29.0: + resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} + dev: true + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -18608,6 +18797,32 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true + /storybook-addon-designs@7.0.0-beta.2(@storybook/addon-docs@7.0.23)(@storybook/addons@7.0.24)(@storybook/api@7.0.23)(@storybook/components@7.0.23)(@storybook/theming@7.0.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ljBNmyCJdPTXhiBSfA1S+GBxtMooW2M7nxlt49OoCRH7jcxZOYQdiI8JYQiMF5Ur0MGakbSci0Xm+JzAvcm02g==} + peerDependencies: + '@storybook/addon-docs': ^6.4.0 || ^7.0.0 + '@storybook/addons': ^6.4.0 || ^7.0.0 + '@storybook/api': ^6.4.0 || ^7.0.0 + '@storybook/components': ^6.4.0 || ^7.0.0 + '@storybook/theming': ^6.4.0 || ^7.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@figspec/react': 1.0.3(react@18.2.0) + '@storybook/addon-docs': 7.0.23(react-dom@18.2.0)(react@18.2.0) + '@storybook/addons': 7.0.24(react-dom@18.2.0)(react@18.2.0) + '@storybook/api': 7.0.23(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.0.23(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.23(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /storybook@7.0.23: resolution: {integrity: sha512-eko4DZ6lheJZCsL55RJhYksXX3UWgdO6rkR52pmfhCjlitxf07We+lEuzVou8+HLg8jnSqLi2GIzDKh+hBS4og==} hasBin: true From 8dccbbb0c9dc8e35fa246ebafb02a8109eddc44b Mon Sep 17 00:00:00 2001 From: Nick Grosenbacher Date: Wed, 28 Jun 2023 11:24:19 -0400 Subject: [PATCH 2/3] Update to match DESIGN-1240 - Send to CAVATICA top button and modal button work the same way; if a selection is made, that selection is what will be sent - Update button copy to clarify the current set of actionable results - Update layout of RowSelectionUI --- .../QueryWrapperPlotNav.tsx | 8 +- .../StandaloneQueryWrapper.tsx | 2 +- .../RowSelection/RowSelectionControls.tsx | 4 +- .../RowSelection/RowSelectionUI.tsx | 21 ++- .../SendToCavaticaConfirmationDialog.tsx | 28 +-- .../TopLevelControls.tsx | 114 ++++++++----- .../TopLevelControls/TopLevelControlsUtils.ts | 64 +++++++ .../src/components/styled/InlineBadge.tsx | 20 +++ .../stories/QueryWrapperPlotNav.stories.ts | 3 +- .../stories/RowSelection.stories.tsx | 58 ++++--- ...ndToCavaticaConfirmationDialog.stories.tsx | 6 + .../TopLevelControlsUtils.test.ts | 159 ++++++++++++++++++ 12 files changed, 394 insertions(+), 93 deletions(-) rename packages/synapse-react-client/src/components/SynapseTable/{ => TopLevelControls}/TopLevelControls.tsx (76%) create mode 100644 packages/synapse-react-client/src/components/SynapseTable/TopLevelControls/TopLevelControlsUtils.ts create mode 100644 packages/synapse-react-client/src/components/styled/InlineBadge.tsx create mode 100644 packages/synapse-react-client/test/containers/table/TopLevelControls/TopLevelControlsUtils.test.ts diff --git a/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx b/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx index 430c307806..fa4d00d205 100644 --- a/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx +++ b/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx @@ -33,7 +33,7 @@ import SqlEditor from '../SqlEditor' import { SynapseTableProps } from '../SynapseTable/SynapseTable' import TopLevelControls, { TopLevelControlsProps, -} from '../SynapseTable/TopLevelControls' +} from '../SynapseTable/TopLevelControls/TopLevelControls' import FacetNav, { FacetNavProps } from '../widgets/facet-nav/FacetNav' import FacetFilterControls, { FacetFilterControlsProps, @@ -76,7 +76,8 @@ type QueryWrapperPlotNavOwnProps = { | 'rgbIndex' | 'showLastUpdatedOn' | 'noContentPlaceholderType' - | 'isRowSelectionVisible' + | 'isRowSelectionVisible', + 'unitDescription' > export type SearchParams = { @@ -117,6 +118,7 @@ const QueryWrapperPlotNav: React.FunctionComponent = ( cavaticaHelpURL, customControls, isRowSelectionVisible, + unitDescription, } = props const entityId = parseEntityIdFromSqlStatement(sql) @@ -159,7 +161,7 @@ const QueryWrapperPlotNav: React.FunctionComponent = ( return ( customControl.onClick({ data, selectedRows, refresh }) } @@ -55,7 +54,6 @@ export function RowSelectionControls(props: RowSelectionControlsProps) { {showExportToCavatica && ( - - {' '} - Rows Selected + + + + Rows Selected + + {customControls} -
{customControls}
) } diff --git a/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx b/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx index 46c10b874d..6283466531 100644 --- a/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx +++ b/packages/synapse-react-client/src/components/SynapseTable/SendToCavaticaConfirmationDialog.tsx @@ -14,7 +14,7 @@ import { SkeletonParagraph } from '../Skeleton' import { useExportToCavatica } from '../../synapse-queries/entity/useExportToCavatica' import { useQueryVisualizationContext } from '../QueryVisualizationWrapper' import { cloneDeep } from 'lodash-es' -import pluralize from 'pluralize' +import { getNumberOfResultsToInvokeActionCopy } from './TopLevelControls/TopLevelControlsUtils' export type SendToCavaticaConfirmationDialogProps = { cavaticaHelpURL?: string @@ -24,8 +24,12 @@ export default function SendToCavaticaConfirmationDialog( props: SendToCavaticaConfirmationDialogProps, ) { const { cavaticaHelpURL } = props - const { data, getLastQueryRequest, onViewSharingSettingsClicked } = - useQueryContext() + const { + data, + getLastQueryRequest, + onViewSharingSettingsClicked, + hasResettableFilters, + } = useQueryContext() const { isShowingExportToCavaticaModal, setIsShowingExportToCavaticaModal, @@ -76,17 +80,13 @@ export default function SendToCavaticaConfirmationDialog( const actions: ActionRequiredCount[] | undefined = queryResultBundle?.actionsRequired - let confirmButtonText - if (!hasSelectedRows) { - confirmButtonText = `Send all ${data?.queryCount?.toLocaleString()} ${pluralize( - unitDescription, - )} to CAVATICA` - } else { - confirmButtonText = `Send ${selectedRows.length.toLocaleString()} selected ${pluralize( - unitDescription, - selectedRows.length, - )} to CAVATICA` - } + const confirmButtonText = `Send ${getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + )} to CAVATICA` return ( { cavaticaHelpURL, } = props - const { data, entity, getInitQueryRequest, lockedColumn } = useQueryContext() + const { + data, + entity, + getInitQueryRequest, + lockedColumn, + hasResettableFilters, + } = useQueryContext() const { topLevelControlsState, @@ -106,6 +115,7 @@ const TopLevelControls = (props: TopLevelControlsProps) => { selectedRows, setColumnsToShowInTable, setIsShowingExportToCavaticaModal, + unitDescription, } = useQueryVisualizationContext() const { showCopyToClipboard } = topLevelControlsState @@ -169,6 +179,19 @@ const TopLevelControls = (props: TopLevelControlsProps) => { } const showFacetFilter = topLevelControlsState?.showFacetFilter const hasSelectedRows = isRowSelectionVisible && selectedRows.length > 0 + const numberOfResultsToInvokeAction = getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ) + const numberOfResultsToInvokeActionAsText = + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ) return (
{
{showExportToCavatica && ( - - This action will send a reference to{' '} - {hasSelectedRows - ? 'each selected file' - : 'every file in the current table'}{' '} - to CAVATICA.{' '} - {!hasSelectedRows && - topLevelControlsState.showFacetFilter && ( + <> + + This action will send a reference to{' '} + {hasSelectedRows + ? 'each selected file' + : 'every file in the current table'}{' '} + to CAVATICA.{' '} + {!hasSelectedRows && + topLevelControlsState.showFacetFilter && ( + <> + You can change what is sent by applying filters using + the controls in the sidebar. + + )} + {hasSelectedRows && ( <> - You can change what is sent by applying filters using - the controls in the sidebar. + You can change what is sent by selecting a different set + of files. )} - {hasSelectedRows && ( - <> - You can change what is sent by selecting a different set - of files. - - )} - - } - > - - + + + + )} {controls.map(control => { const { key, icon, tooltipText } = control diff --git a/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls/TopLevelControlsUtils.ts b/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls/TopLevelControlsUtils.ts new file mode 100644 index 0000000000..dfa6e595ce --- /dev/null +++ b/packages/synapse-react-client/src/components/SynapseTable/TopLevelControls/TopLevelControlsUtils.ts @@ -0,0 +1,64 @@ +import { QueryContextType } from '../../QueryContext' +import { QueryVisualizationContextType } from '../../QueryVisualizationWrapper' +import pluralize from 'pluralize' + +/** + * If the user invokes an action on the table (such as "Send to CAVATICA"), we want to tell the user how many rows they + * will perform the action on. + * + * If selection is enabled and rows have been selected, return the number of selected rows. + * If no rows have been selected, return the total number of query results. + * If the total number of query results is unknown, return undefined. + * @param isRowSelectionVisible + * @param selectedRows + * @param data + */ +export function getNumberOfResultsToInvokeAction( + isRowSelectionVisible: QueryVisualizationContextType['isRowSelectionVisible'], + selectedRows: QueryVisualizationContextType['selectedRows'], + data: QueryContextType['data'], +) { + const hasSelectedRows = isRowSelectionVisible && selectedRows.length > 0 + return hasSelectedRows ? selectedRows.length : data?.queryCount +} + +/** + * Returns copy for how to reference the number of results that will be affected by an action, such as "Send to CAVATICA". + * Utilizes the unit description to return a count and pluralized unit description. + * + * If the user has not selected rows or applied any filters, return "all s". + * If the user has applied filters but not selected rows, return the total number of query results to indicate that the filtered result set is used. e.g. '2 s' + * - If the total number of query results is unknown, the count is not included. e.g. 's' + * If the user has selected rows, return the number of selected rows. e.g. '2 s' + * @param hasResettableFilters + * @param isRowSelectionVisible + * @param selectedRows + * @param data + * @param unitDescription + */ +export function getNumberOfResultsToInvokeActionCopy( + hasResettableFilters: QueryContextType['hasResettableFilters'], + isRowSelectionVisible: QueryVisualizationContextType['isRowSelectionVisible'], + selectedRows: QueryVisualizationContextType['selectedRows'], + data: QueryContextType['data'], + unitDescription: QueryVisualizationContextType['unitDescription'], +) { + const hasSelectedRows = isRowSelectionVisible && selectedRows.length > 0 + if (!hasResettableFilters && !hasSelectedRows) { + return `all ${pluralize(unitDescription)}` + } + + const numberOfResultsToInvokeAction = getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ) + if (numberOfResultsToInvokeAction != null) { + return `${numberOfResultsToInvokeAction.toLocaleString()} ${pluralize( + unitDescription, + numberOfResultsToInvokeAction, + )}` + } + // Null count, so just return the pluralized unit description + return pluralize(unitDescription) +} diff --git a/packages/synapse-react-client/src/components/styled/InlineBadge.tsx b/packages/synapse-react-client/src/components/styled/InlineBadge.tsx new file mode 100644 index 0000000000..3b496650c6 --- /dev/null +++ b/packages/synapse-react-client/src/components/styled/InlineBadge.tsx @@ -0,0 +1,20 @@ +import { Badge, BadgeProps, styled } from '@mui/material' +import { StyledComponent } from '@emotion/styled' + +/** + * Renders a badge component in-line with other content, instead of as an overlay. + */ +export const InlineBadge: StyledComponent = styled(Badge, { + label: 'InlineBadge', +})(({ theme }) => ({ + position: 'static', + justifyContent: 'flex-end', + alignItems: 'center', + '.MuiBadge-badge': { + minWidth: 'unset', + position: 'relative', + transform: 'none', + }, +})) + +export default InlineBadge diff --git a/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts b/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts index 3b7f554fbf..143586f15c 100644 --- a/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts +++ b/packages/synapse-react-client/stories/QueryWrapperPlotNav.stories.ts @@ -15,7 +15,7 @@ import { ColumnSingleValueFilterOperator, } from '@sage-bionetworks/synapse-types' import { displayToast } from '../src/components/ToastMessage' -import { CustomControlCallbackData } from '../src/components/SynapseTable/TopLevelControls' +import { CustomControlCallbackData } from '../src/components/SynapseTable/TopLevelControls/TopLevelControls' import { QUERY_FILTERS_LOCAL_STORAGE_KEY } from '../src/utils/functions/SqlFunctions' const meta = { @@ -191,6 +191,7 @@ export const SendToCavatica: Story = { showAccessColumn: true, showDownloadColumn: true, }, + unitDescription: 'file', name: 'CAVATICA Integration Demo', hideSqlEditorControl: false, shouldDeepLink: false, diff --git a/packages/synapse-react-client/stories/RowSelection.stories.tsx b/packages/synapse-react-client/stories/RowSelection.stories.tsx index 0e0c6dc268..47bc0deb3c 100644 --- a/packages/synapse-react-client/stories/RowSelection.stories.tsx +++ b/packages/synapse-react-client/stories/RowSelection.stories.tsx @@ -4,10 +4,45 @@ import { displayToast } from '../src' import { GetApp } from '@mui/icons-material' import { RowSelectionUI } from '../src/components/SynapseTable/RowSelection/RowSelectionUI' import { Button } from '@mui/material' +import { times } from 'lodash-es' const meta = { title: 'Explore/RowSelection', component: RowSelectionUI, + argTypes: { + numberOfActions: { + type: 'number', + description: 'The number of actions to display', + }, + }, + render: args => { + const { numberOfActions = 0, ...props } = args + return ( + + {times(numberOfActions, i => ( + + ))} + + } + /> + ) + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/3l3RjDnKnv8jms2XFR5BQu/Main?type=design&node-id=462-39826', + }, + }, } satisfies Meta export default meta @@ -18,25 +53,10 @@ export const Demo: Story = { name: 'RowSelection', args: { show: true, - customControls: ( - <> - - - - ), selectedRowCount: 5, + onClearSelection: () => { + displayToast('clear selection called') + }, + numberOfActions: 2, }, } diff --git a/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx b/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx index 91972bdfcc..4fdee73981 100644 --- a/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx +++ b/packages/synapse-react-client/stories/SendToCavaticaConfirmationDialog.stories.tsx @@ -24,6 +24,12 @@ const meta = { control: { type: 'text' }, }, }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/3l3RjDnKnv8jms2XFR5BQu/Main?type=design&node-id=1909-58523', + }, + }, } satisfies Meta export default meta type Story = StoryObj diff --git a/packages/synapse-react-client/test/containers/table/TopLevelControls/TopLevelControlsUtils.test.ts b/packages/synapse-react-client/test/containers/table/TopLevelControls/TopLevelControlsUtils.test.ts new file mode 100644 index 0000000000..ab88b1c82b --- /dev/null +++ b/packages/synapse-react-client/test/containers/table/TopLevelControls/TopLevelControlsUtils.test.ts @@ -0,0 +1,159 @@ +import { getSchemaForPropertyType } from '../../../../src/components/SchemaDrivenAnnotationEditor/field/AdditionalPropertiesSchemaField' +import { + getNumberOfResultsToInvokeAction, + getNumberOfResultsToInvokeActionCopy, +} from '../../../../src/components/SynapseTable/TopLevelControls/TopLevelControlsUtils' +import { QueryResultBundle, Row } from '@sage-bionetworks/synapse-types' + +describe('TopLevelControlsUtils', () => { + describe('getNumberOfResultsToInvokeAction', () => { + it('has selected rows', () => { + const isRowSelectionVisible = true + const selectedRows = [{}, {}] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + expect( + getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ), + ).toEqual(2) + }) + it('has no selected rows', () => { + const isRowSelectionVisible = true + const selectedRows = [] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + expect( + getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ), + ).toEqual(200) + }) + it('selection is disabled, total result count is available', () => { + const isRowSelectionVisible = false + const selectedRows = [] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + expect( + getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ), + ).toEqual(200) + }) + it('selection is disabled, total result count is not available', () => { + const isRowSelectionVisible = false + const selectedRows = [] as Row[] + const data = undefined + expect( + getNumberOfResultsToInvokeAction( + isRowSelectionVisible, + selectedRows, + data, + ), + ).toEqual(undefined) + }) + }) + + describe('getNumberOfResultsToInvokeActionCopy', () => { + it('is unfiltered and selection is disabled', () => { + const hasResettableFilters = false + const isRowSelectionVisible = false + const selectedRows = [] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('all files') + }) + it('is unfiltered and has no selected rows', () => { + const hasResettableFilters = false + const isRowSelectionVisible = true + const selectedRows = [] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('all files') + }) + it('has selected rows', () => { + const hasResettableFilters = false + const isRowSelectionVisible = true + const selectedRows = [{}, {}] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('2 files') + }) + it('is filtered without selection', () => { + const hasResettableFilters = true + const isRowSelectionVisible = false + const selectedRows = [] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('200 files') + }) + it('is filtered with selection', () => { + const hasResettableFilters = true + const isRowSelectionVisible = true + const selectedRows = [{}, {}] as Row[] + const data = { queryCount: 200 } as QueryResultBundle + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('2 files') + }) + + it('is filtered without selection, count is not available', () => { + const hasResettableFilters = true + const isRowSelectionVisible = false + const selectedRows = [] as Row[] + const data = undefined + const unitDescription = 'file' + expect( + getNumberOfResultsToInvokeActionCopy( + hasResettableFilters, + isRowSelectionVisible, + selectedRows, + data, + unitDescription, + ), + ).toEqual('files') + }) + }) +}) From b15514e37b5d45dcd16ad9a27d5f158d1476e712 Mon Sep 17 00:00:00 2001 From: Nick Grosenbacher Date: Thu, 29 Jun 2023 14:23:53 -0400 Subject: [PATCH 3/3] PORTALS-2701 - Send row selection to CAVATICA - QueryVisualizationWrapper: add rowSelectionPrimaryKey prop to determine how filtering on row selection should work. Defaults to `['id']` for file views and datasets. - SendToCavaticaConfirmationDialog: refactor and encapsulate query to get actions required in new hook `useGetActionsRequiredForTableEntity` - ConfirmationDialog: fix confirmButtonDisabled prop not propagating to button - add tests to confirm actions required blocks export to CAVATICA action --- .../ConfirmationDialog/ConfirmationDialog.tsx | 2 + .../components/QueryVisualizationWrapper.tsx | 28 +++++++++-- .../QueryWrapperPlotNav.tsx | 34 +++++++------ .../RowSelection/RowSelectionUI.tsx | 2 +- .../SendToCavaticaConfirmationDialog.tsx | 48 +++++++++---------- .../components/SynapseTable/SynapseTable.tsx | 16 ++----- .../SynapseTable/SynapseTableUtils.ts | 18 +++++++ .../entity/useActionsRequiredForTableQuery.ts | 46 ++++++++++++++++++ .../entity/useGetQueryResultBundle.ts | 39 ++++++++++----- .../SendToCavaticaConfirmationDialog.test.tsx | 46 +++++++++++++++++- 10 files changed, 209 insertions(+), 70 deletions(-) create mode 100644 packages/synapse-react-client/src/synapse-queries/entity/useActionsRequiredForTableQuery.ts diff --git a/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx index 3171939231..cea191d325 100644 --- a/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx +++ b/packages/synapse-react-client/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -61,6 +61,7 @@ export function ConfirmationDialog(props: ConfirmationDialogProps) { confirmButtonClassName, confirmButtonColor, confirmButtonVariant, + confirmButtonDisabled, onConfirm, onCancel, hasCancelButton, @@ -75,6 +76,7 @@ export function ConfirmationDialog(props: ConfirmationDialogProps) { confirmButtonClassName={confirmButtonClassName} confirmButtonColor={confirmButtonColor} confirmButtonVariant={confirmButtonVariant} + confirmButtonDisabled={confirmButtonDisabled} onConfirm={onConfirm} onCancel={onCancel} hasCancelButton={hasCancelButton} diff --git a/packages/synapse-react-client/src/components/QueryVisualizationWrapper.tsx b/packages/synapse-react-client/src/components/QueryVisualizationWrapper.tsx index a3b8250d92..cbff9cb588 100644 --- a/packages/synapse-react-client/src/components/QueryVisualizationWrapper.tsx +++ b/packages/synapse-react-client/src/components/QueryVisualizationWrapper.tsx @@ -14,6 +14,7 @@ import ThisTableIsEmpty from './SynapseTable/TableIsEmpty' import { unCamelCase } from '../utils/functions/unCamelCase' import { ColumnType, Row } from '@sage-bionetworks/synapse-types' import { getDisplayValue } from '../utils/functions/getDataFromFromStorage' +import { isFileViewOrDataset } from './SynapseTable/SynapseTableUtils' export type QueryVisualizationContextType = { topLevelControlsState: TopLevelControlsState @@ -39,6 +40,10 @@ export type QueryVisualizationContextType = { setIsShowingExportToCavaticaModal: React.Dispatch< React.SetStateAction > + /** The set of columns that defines a uniqueness constraint on the table for the purposes of filtering based on row selection. + * Note that Synapse tables have no internal concept of a primary key. + */ + rowSelectionPrimaryKey?: string[] } /** @@ -94,6 +99,10 @@ export type QueryVisualizationWrapperProps = { /** Default is INTERACTIVE */ noContentPlaceholderType?: NoContentPlaceholderType isRowSelectionVisible?: boolean + /** The set of columns that defines a uniqueness constraint on the table for the purposes of filtering based on row selection. + * Note that Synapse tables have no internal concept of a primary key. + */ + rowSelectionPrimaryKey?: string[] } export type TopLevelControlsState = { @@ -117,12 +126,22 @@ export function QueryVisualizationWrapper( const { noContentPlaceholderType = NoContentPlaceholderType.INTERACTIVE, isRowSelectionVisible = false, + columnAliases = {}, } = props - const { data, getLastQueryRequest, isFacetsAvailable, hasResettableFilters } = - useQueryContext() - - const { columnAliases = {} } = props + const { + data, + entity, + getLastQueryRequest, + isFacetsAvailable, + hasResettableFilters, + } = useQueryContext() + + let { rowSelectionPrimaryKey } = props + if (!rowSelectionPrimaryKey && isFileViewOrDataset(entity)) { + // If the primary key isn't specified on a file view/dataset, we can safely use the 'id' column + rowSelectionPrimaryKey = ['id'] + } const [topLevelControlsState, setTopLevelControlsState] = useState({ @@ -212,6 +231,7 @@ export function QueryVisualizationWrapper( isRowSelectionVisible, isShowingExportToCavaticaModal, setIsShowingExportToCavaticaModal, + rowSelectionPrimaryKey, } /** * Render the children without any formatting diff --git a/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx b/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx index fa4d00d205..7def51865b 100644 --- a/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx +++ b/packages/synapse-react-client/src/components/QueryWrapperPlotNav/QueryWrapperPlotNav.tsx @@ -41,6 +41,7 @@ import FacetFilterControls, { import FilterAndView from './FilterAndView' import { NoContentPlaceholderType } from '../SynapseTable/NoContentPlaceholderType' import { Box } from '@mui/material' +import { SynapseErrorBoundary } from '../error/ErrorBanner' type QueryWrapperPlotNavOwnProps = { sql: string @@ -76,8 +77,9 @@ type QueryWrapperPlotNavOwnProps = { | 'rgbIndex' | 'showLastUpdatedOn' | 'noContentPlaceholderType' - | 'isRowSelectionVisible', - 'unitDescription' + | 'isRowSelectionVisible' + | 'unitDescription' + | 'rowSelectionPrimaryKey' > export type SearchParams = { @@ -119,6 +121,7 @@ const QueryWrapperPlotNav: React.FunctionComponent = ( customControls, isRowSelectionVisible, unitDescription, + rowSelectionPrimaryKey, } = props const entityId = parseEntityIdFromSqlStatement(sql) @@ -162,6 +165,7 @@ const QueryWrapperPlotNav: React.FunctionComponent = ( = ( queryVisualizationContext.setTopLevelControlsState } /> - + + + {isFaceted && ( <> 0 @@ -46,12 +45,18 @@ export default function SendToCavaticaConfirmationDialog( if (!hasSelectedRows) { return request } else { + if (!rowSelectionPrimaryKey || rowSelectionPrimaryKey.length !== 1) { + // TODO: Handle composite/undefined key + throw new Error( + 'rowSelectionPrimaryKey must be defined and have length 1', + ) + } // Add a filter that will just return the selected rows. const idColIndex = data?.columnModels?.findIndex(cm => cm.name === 'id') const idColumnFilter: ColumnSingleValueQueryFilter = { concreteType: 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter', - columnName: 'id', + columnName: rowSelectionPrimaryKey[0], operator: ColumnSingleValueFilterOperator.IN, values: selectedRows!.map(row => row.values[idColIndex!]!), } @@ -67,25 +72,16 @@ export default function SendToCavaticaConfirmationDialog( cavaticaQueryRequest, data?.queryResult?.queryResults.headers, ) - const queryRequestCopy = useMemo(() => { - const request = cloneDeep(cavaticaQueryRequest) - const fileColumnId = getFileColumnModelId(data?.columnModels) - if (fileColumnId) { - request.query.selectFileColumn = Number(fileColumnId) - } - request.partMask = SynapseConstants.BUNDLE_MASK_ACTIONS_REQUIRED - return request - }, [cavaticaQueryRequest, data?.columnModels]) - const { data: asyncJobStatus, isLoading } = - useGetQueryResultBundleWithAsyncStatus(queryRequestCopy, { - enabled: fileColumnId !== undefined, + const { data: actions, isLoading } = useGetActionsRequiredForTableQuery( + cavaticaQueryRequest, + data?.columnModels as ColumnModel[], + undefined, + { useErrorBoundary: true, - }) - - const queryResultBundle = asyncJobStatus?.responseBody - const actions: ActionRequiredCount[] | undefined = - queryResultBundle?.actionsRequired + enabled: !!data?.columnModels, + }, + ) const confirmButtonText = `Send ${getNumberOfResultsToInvokeActionCopy( hasResettableFilters, @@ -168,7 +164,7 @@ export default function SendToCavaticaConfirmationDialog( <> You must also take these actions before sending the selected data to CAVATICA: @@ -183,7 +179,7 @@ export default function SendToCavaticaConfirmationDialog( You must take the following actions before we can send this data to CAVATICA. - + {actions.map((item: ActionRequiredCount, index) => { if (item) { return ( @@ -198,7 +194,7 @@ export default function SendToCavaticaConfirmationDialog( ) } else return false })} - + ) )} diff --git a/packages/synapse-react-client/src/components/SynapseTable/SynapseTable.tsx b/packages/synapse-react-client/src/components/SynapseTable/SynapseTable.tsx index e9c647702b..e8f30b1b09 100644 --- a/packages/synapse-react-client/src/components/SynapseTable/SynapseTable.tsx +++ b/packages/synapse-react-client/src/components/SynapseTable/SynapseTable.tsx @@ -4,7 +4,6 @@ import React from 'react' import { DialogBase } from '../DialogBase' import SynapseClient from '../../synapse-client' import { - hasFilesInView, isDataset, isDatasetCollection, isEntityView, @@ -48,6 +47,7 @@ import { ICON_STATE } from './SynapseTableConstants' import { getColumnIndicesWithType, getUniqueEntities, + isFileViewOrDataset, } from './SynapseTableUtils' import { TablePagination } from './TablePagination' import EntityIDColumnCopyIcon from './EntityIDColumnCopyIcon' @@ -426,15 +426,6 @@ export class SynapseTable extends React.Component< showDownloadColumn, } = this.props - /** - * i.e. the view may have FileEntities in it - * - * PORTALS-2010: Enhance change made for PORTALS-1973. File specific action will only be shown for rows that represent FileEntities. - */ - const isFileViewOrDataset = - entity && - ((isEntityView(entity) && hasFilesInView(entity)) || isDataset(entity)) - const isShowingAccessColumn: boolean | undefined = showAccessColumn && entity && @@ -443,7 +434,10 @@ export class SynapseTable extends React.Component< const isLoggedIn = !!this.props.synapseContext.accessToken const rowsAreDownloadable = - entity && isFileViewOrDataset && isLoggedIn && this.allRowsHaveId() + entity && + isFileViewOrDataset(entity) && + isLoggedIn && + this.allRowsHaveId() const isShowingAddToV2DownloadListColumn: boolean = !!( rowsAreDownloadable && !this.props.hideDownload diff --git a/packages/synapse-react-client/src/components/SynapseTable/SynapseTableUtils.ts b/packages/synapse-react-client/src/components/SynapseTable/SynapseTableUtils.ts index 8a2b8e3ef1..2f8787592b 100644 --- a/packages/synapse-react-client/src/components/SynapseTable/SynapseTableUtils.ts +++ b/packages/synapse-react-client/src/components/SynapseTable/SynapseTableUtils.ts @@ -4,9 +4,15 @@ import { ColumnTypeEnum, EntityHeader, QueryResultBundle, + Table, UserGroupHeader, UserProfile, } from '@sage-bionetworks/synapse-types' +import { + hasFilesInView, + isDataset, + isEntityView, +} from '../../utils/functions/EntityTypeUtils' export const getColumnIndicesWithType = ( data: QueryResultBundle | undefined, @@ -46,6 +52,18 @@ export const getUniqueEntities = ( return distinctEntities } +/** + * i.e. the view may have FileEntities in it + * + * PORTALS-2010: Enhance change made for PORTALS-1973. File specific action will only be shown for rows that represent FileEntities. + */ +export function isFileViewOrDataset(entity?: Table) { + return ( + entity && + ((isEntityView(entity) && hasFilesInView(entity)) || isDataset(entity)) + ) +} + export const getFileColumnModelId = ( columnModels?: ColumnModel[], ): string | undefined => { diff --git a/packages/synapse-react-client/src/synapse-queries/entity/useActionsRequiredForTableQuery.ts b/packages/synapse-react-client/src/synapse-queries/entity/useActionsRequiredForTableQuery.ts new file mode 100644 index 0000000000..b9e8159e7b --- /dev/null +++ b/packages/synapse-react-client/src/synapse-queries/entity/useActionsRequiredForTableQuery.ts @@ -0,0 +1,46 @@ +import { + ActionRequiredCount, + AsynchronousJobStatus, + ColumnModel, + QueryBundleRequest, + QueryResultBundle, +} from '@sage-bionetworks/synapse-types' +import { useMemo } from 'react' +import { cloneDeep } from 'lodash-es' +import { getFileColumnModelId } from '../../components/SynapseTable/SynapseTableUtils' +import { SynapseConstants } from '../../utils' +import { useGetQueryResultBundleWithAsyncStatus } from './useGetQueryResultBundle' +import { UseQueryOptions } from 'react-query' +import { SynapseClientError } from '../../utils/SynapseClientError' + +export function useGetActionsRequiredForTableQuery( + queryBundleRequest: QueryBundleRequest, + columnModels: ColumnModel[], + fileColumnModelId?: number, + options?: UseQueryOptions< + AsynchronousJobStatus, + SynapseClientError, + ActionRequiredCount[] + >, +) { + const queryRequestCopy = useMemo(() => { + const request = cloneDeep(queryBundleRequest) + const fileColumnId = fileColumnModelId || getFileColumnModelId(columnModels) + request.query.selectFileColumn = Number(fileColumnId) + request.partMask = SynapseConstants.BUNDLE_MASK_ACTIONS_REQUIRED + return request + }, [columnModels, fileColumnModelId, queryBundleRequest]) + + return useGetQueryResultBundleWithAsyncStatus( + queryRequestCopy, + { + ...options, + enabled: + (options?.enabled ?? true) && + queryRequestCopy.query.selectFileColumn !== undefined, + select: data => { + return data?.responseBody?.actionsRequired! + }, + }, + ) +} diff --git a/packages/synapse-react-client/src/synapse-queries/entity/useGetQueryResultBundle.ts b/packages/synapse-react-client/src/synapse-queries/entity/useGetQueryResultBundle.ts index c853b2a716..3df6f04b1b 100644 --- a/packages/synapse-react-client/src/synapse-queries/entity/useGetQueryResultBundle.ts +++ b/packages/synapse-react-client/src/synapse-queries/entity/useGetQueryResultBundle.ts @@ -50,11 +50,14 @@ export default function useGetQueryResultBundle( ) } -function _useGetQueryResultBundleWithAsyncStatus( +function _useGetQueryResultBundleWithAsyncStatus< + TData = AsynchronousJobStatus, +>( queryBundleRequest: QueryBundleRequest, options?: UseQueryOptions< AsynchronousJobStatus, - SynapseClientError + SynapseClientError, + TData >, setCurrentAsyncStatus?: ( status: AsynchronousJobStatus, @@ -64,7 +67,8 @@ function _useGetQueryResultBundleWithAsyncStatus( return useQuery< AsynchronousJobStatus, - SynapseClientError + SynapseClientError, + TData >( keyFactory.getEntityTableQueryResultWithAsyncStatusQueryKey( queryBundleRequest, @@ -83,11 +87,14 @@ function _useGetQueryResultBundleWithAsyncStatus( ) } -function useGetQueryRows( +function useGetQueryRows< + TData = AsynchronousJobStatus, +>( queryBundleRequest: QueryBundleRequest, options?: UseQueryOptions< AsynchronousJobStatus, - SynapseClientError + SynapseClientError, + TData >, setCurrentAsyncStatus?: ( status: AsynchronousJobStatus, @@ -103,7 +110,7 @@ function useGetQueryRows( const enableQuery = queryRowsBundleRequestMask > 0 ? options?.enabled : false - return _useGetQueryResultBundleWithAsyncStatus( + return _useGetQueryResultBundleWithAsyncStatus( rowsOnlyQueryBundleRequest, { ...options, @@ -113,11 +120,14 @@ function useGetQueryRows( ) } -function useGetQueryStats( +function useGetQueryStats< + TData = AsynchronousJobStatus, +>( queryBundleRequest: QueryBundleRequest, options?: UseQueryOptions< AsynchronousJobStatus, - SynapseClientError + SynapseClientError, + TData >, setCurrentAsyncStatus?: ( status: AsynchronousJobStatus, @@ -140,7 +150,7 @@ function useGetQueryStats( const enableQuery = queryStatsMask > 0 ? options?.enabled : false - return _useGetQueryResultBundleWithAsyncStatus( + return _useGetQueryResultBundleWithAsyncStatus( queryStatsRequest, { ...options, @@ -150,11 +160,14 @@ function useGetQueryStats( ) } -export function useGetQueryResultBundleWithAsyncStatus( +export function useGetQueryResultBundleWithAsyncStatus< + TData = AsynchronousJobStatus, +>( queryBundleRequest: QueryBundleRequest, options?: UseQueryOptions< AsynchronousJobStatus, - SynapseClientError + SynapseClientError, + TData >, setCurrentAsyncStatus?: ( status: AsynchronousJobStatus, @@ -165,12 +178,12 @@ export function useGetQueryResultBundleWithAsyncStatus( * - Query result rows, which will change each page * - Everything else, which does not change each page */ - const rowResult = useGetQueryRows( + const rowResult = useGetQueryRows( queryBundleRequest, options, setCurrentAsyncStatus, ) - const statsResult = useGetQueryStats( + const statsResult = useGetQueryStats( queryBundleRequest, options, setCurrentAsyncStatus, diff --git a/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx b/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx index c2ebcf3f83..2f22463e77 100644 --- a/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx +++ b/packages/synapse-react-client/test/containers/table/SendToCavaticaConfirmationDialog.test.tsx @@ -19,7 +19,10 @@ import { } from '../../../src/components/QueryVisualizationWrapper' import { ColumnSingleValueFilterOperator } from '@sage-bionetworks/synapse-types' import { cloneDeep } from 'lodash-es' +import { mockManagedACTAccessRequirement } from '../../../mocks/mockAccessRequirements' import * as UseExportToCavaticaModule from '../../../src/synapse-queries/entity/useExportToCavatica' +import * as UseActionsRequiredForTableQueryModule from '../../../src/synapse-queries/entity/useActionsRequiredForTableQuery' +import * as ActionRequiredListItem from '../../../src/components/DownloadCart/ActionRequiredListItem' const onExportToCavatica = jest.fn() @@ -29,6 +32,13 @@ const mockUseExportToCavatica = jest return onExportToCavatica }) +const mockUseGetActionsRequiredForTableQuery = jest + .spyOn( + UseActionsRequiredForTableQueryModule, + 'useGetActionsRequiredForTableQuery', + ) + .mockReturnValue({ isLoading: false, data: [] }) + function renderComponent( wrapperProps?: SynapseContextType, queryContextOverrides?: Partial, @@ -52,6 +62,7 @@ function renderComponent( selectedRows: [], resultsToExportToCavatica: 'ALL', unitDescription: 'result', + rowSelectionPrimaryKey: ['id'], ...queryVisualizationContextOverrides, }} > @@ -79,7 +90,7 @@ function setUp( return { component, user, sendToCavatica } } -describe('Export to CAVATICA Modal', () => { +describe('Send to CAVATICA Confirmation Dialog', () => { beforeEach(() => { jest.clearAllMocks() }) @@ -129,4 +140,37 @@ describe('Export to CAVATICA Modal', () => { mockQueryResultBundle.queryResult.queryResults.headers, ) }) + + it('blocks submission while loading actions required', async () => { + mockUseGetActionsRequiredForTableQuery.mockReturnValue({ + isLoading: true, + data: undefined, + }) + const { user, sendToCavatica } = setUp(undefined, undefined, { + isRowSelectionVisible: false, + selectedRows: [], + }) + + expect(sendToCavatica).toBeDisabled() + }) + it('blocks submission if actions required exist', async () => { + mockUseGetActionsRequiredForTableQuery.mockReturnValue({ + isLoading: false, + data: [ + { + action: { + concreteType: 'org.sagebionetworks.repo.model.download.EnableTwoFa', + accessRequirementId: mockManagedACTAccessRequirement.id, + }, + count: 1, + }, + ], + }) + const { user, sendToCavatica } = setUp(undefined, undefined, { + isRowSelectionVisible: false, + selectedRows: [], + }) + + expect(sendToCavatica).toBeDisabled() + }) })