diff --git a/.pnp.cjs b/.pnp.cjs index bff18cd7d7..618dd9c6a2 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -150,7 +150,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-notifications-component", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:4.0.1"],\ ["react-oidc-context", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:2.1.0"],\ ["react-paginating", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:1.4.0"],\ - ["react-select", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.7.4"],\ ["react-simplemde-editor", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.0.2"],\ ["react-textarea-autosize", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:8.5.2"],\ ["react-transition-group", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:4.4.5"],\ @@ -3884,10 +3883,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:78df86ad5dfe72b608fb496d44e416cd9611c8aacb1e1a81ae9d184ee55a841bba22bcc747279d3f6f304cb25a2b6d16cdf32a99dd32370f375b22988ff23963#npm:11.11.1", {\ - "packageLocation": "./.yarn/__virtual__/@emotion-react-virtual-3618d80bf9/0/cache/@emotion-react-npm-11.11.1-a07d6a6ebd-aec3c36650.zip/node_modules/@emotion/react/",\ + ["virtual:cf0e3993392eb61e8a3ccdd4da5ec26fefceeac48fef2dac57469d3339f0715075e9dc723aebea551df66fc9d42df413f8c44cdfc2a3fd42d7f3f4d09016aae2#npm:11.11.1", {\ + "packageLocation": "./.yarn/__virtual__/@emotion-react-virtual-ea568e8222/0/cache/@emotion-react-npm-11.11.1-a07d6a6ebd-aec3c36650.zip/node_modules/@emotion/react/",\ "packageDependencies": [\ - ["@emotion/react", "virtual:78df86ad5dfe72b608fb496d44e416cd9611c8aacb1e1a81ae9d184ee55a841bba22bcc747279d3f6f304cb25a2b6d16cdf32a99dd32370f375b22988ff23963#npm:11.11.1"],\ + ["@emotion/react", "virtual:cf0e3993392eb61e8a3ccdd4da5ec26fefceeac48fef2dac57469d3339f0715075e9dc723aebea551df66fc9d42df413f8c44cdfc2a3fd42d7f3f4d09016aae2#npm:11.11.1"],\ ["@babel/runtime", "npm:7.23.8"],\ ["@emotion/babel-plugin", "npm:11.11.0"],\ ["@emotion/cache", "npm:11.11.0"],\ @@ -8112,7 +8111,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-focus-lock", "virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:2.9.5"],\ ["react-modal", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:3.16.1"],\ ["react-onclickoutside", "virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:6.13.0"],\ - ["react-select", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.7.4"],\ + ["react-select", "virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:5.7.4"],\ ["react-switch", "virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:6.1.0"],\ ["typescript", "patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=5da071"],\ ["vite", "virtual:6fecf1af4cab542f4a06b7ce7d9f710277dce92700e0011a9519e41948eed6d8f54c9d0aa109ead6cf4295edce81cb49620f9e823313e99632229bf20d133cdb#npm:4.5.2"],\ @@ -21529,13 +21528,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.7.4", {\ - "packageLocation": "./.yarn/__virtual__/react-select-virtual-78df86ad5d/0/cache/react-select-npm-5.7.4-a84a65df36-ca72941ad1.zip/node_modules/react-select/",\ + ["virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:5.7.4", {\ + "packageLocation": "./.yarn/__virtual__/react-select-virtual-cf0e399339/0/cache/react-select-npm-5.7.4-a84a65df36-ca72941ad1.zip/node_modules/react-select/",\ "packageDependencies": [\ - ["react-select", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.7.4"],\ + ["react-select", "virtual:3cbce82a859ed6a4d5bf57a6b809aea2976beb02c6e1078f18e5e40f750e781222e51bcef76699e2500e623832ea4e45e9647ae7b14a57290a182e58c3827fe8#npm:5.7.4"],\ ["@babel/runtime", "npm:7.23.8"],\ ["@emotion/cache", "npm:11.11.0"],\ - ["@emotion/react", "virtual:78df86ad5dfe72b608fb496d44e416cd9611c8aacb1e1a81ae9d184ee55a841bba22bcc747279d3f6f304cb25a2b6d16cdf32a99dd32370f375b22988ff23963#npm:11.11.1"],\ + ["@emotion/react", "virtual:cf0e3993392eb61e8a3ccdd4da5ec26fefceeac48fef2dac57469d3339f0715075e9dc723aebea551df66fc9d42df413f8c44cdfc2a3fd42d7f3f4d09016aae2#npm:11.11.1"],\ ["@floating-ui/dom", "npm:1.5.4"],\ ["@types/react", "npm:18.2.47"],\ ["@types/react-dom", "npm:18.2.7"],\ @@ -21561,7 +21560,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-select", "virtual:f14c7c2b623bfe35a608f25e5b8f5582c71bc9475deb0d9650188d23bcf292a9531c8cce96f863c9a827a93aa74f4453675de981d038283eed4a2d8e81e490f7#npm:5.7.4"],\ ["@babel/runtime", "npm:7.23.8"],\ ["@emotion/cache", "npm:11.11.0"],\ - ["@emotion/react", "virtual:78df86ad5dfe72b608fb496d44e416cd9611c8aacb1e1a81ae9d184ee55a841bba22bcc747279d3f6f304cb25a2b6d16cdf32a99dd32370f375b22988ff23963#npm:11.11.1"],\ + ["@emotion/react", "virtual:cf0e3993392eb61e8a3ccdd4da5ec26fefceeac48fef2dac57469d3339f0715075e9dc723aebea551df66fc9d42df413f8c44cdfc2a3fd42d7f3f4d09016aae2#npm:11.11.1"],\ ["@floating-ui/dom", "npm:1.5.4"],\ ["@types/react", "npm:18.2.47"],\ ["@types/react-dom", null],\ @@ -23468,7 +23467,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-notifications-component", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:4.0.1"],\ ["react-oidc-context", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:2.1.0"],\ ["react-paginating", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:1.4.0"],\ - ["react-select", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.7.4"],\ ["react-simplemde-editor", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:5.0.2"],\ ["react-textarea-autosize", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:8.5.2"],\ ["react-transition-group", "virtual:3bc50e11628962e2d3d040387b897fa78149010dc0c7837774133f031c81b063c69d97afa708f6f7b77daf0a0d6419898bc26265121b2bec06dfc7ebf0feed1d#npm:4.4.5"],\ diff --git a/package.json b/package.json index 4c20bc1c3e..c7820bfc5f 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "react-notifications-component": "^4.0.1", "react-oidc-context": "^2.1.0", "react-paginating": "^1.4.0", - "react-select": "^5.7.4", "react-simplemde-editor": "^5.0.2", "react-textarea-autosize": "^8.5.2", "react-transition-group": "^4.4.5", diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index f877eff7f3..f6e70c20ca 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -18,3 +18,4 @@ export * from './Spinner'; export * from './Switch'; export * from './theme'; export * from './TooltipTrigger'; +export * from './useModalHandler'; diff --git a/src/components/useModalHandler.test.ts b/packages/components/src/useModalHandler.test.tsx similarity index 66% rename from src/components/useModalHandler.test.ts rename to packages/components/src/useModalHandler.test.tsx index 3503ef884f..e1b21d21f9 100644 --- a/src/components/useModalHandler.test.ts +++ b/packages/components/src/useModalHandler.test.tsx @@ -1,11 +1,11 @@ -import { Modal } from '@terra-ui-packages/components'; import { fireEvent, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; -import { div, h, span } from 'react-hyperscript-helpers'; -import { ButtonPrimary } from 'src/components/common'; -import { useModalHandler } from 'src/components/useModalHandler'; -import { renderWithAppContexts as render } from 'src/testing/test-utils'; + +import { ButtonPrimary } from './buttons'; +import { renderWithTheme as render } from './internal/test-utils'; +import { Modal } from './Modal'; +import { useModalHandler } from './useModalHandler'; describe('useModalHandler helper hook', () => { interface TestComponentProps { @@ -17,52 +17,37 @@ describe('useModalHandler helper hook', () => { /* in most cases, usage will leverage a custom variant of Modal that streamlines the hook usage a bit, but we are using raw Modal since it is sufficient for testing */ - const myModal = useModalHandler((args: string, close: () => void) => - h( - Modal, - { - title: `Modal for ${args}.`, - onDismiss: () => { + const myModal = useModalHandler((args: string, close: () => void) => { + return ( + { close(); onCloseThing(args); - }, - okButton: () => { + }} + okButton={() => { close(); onSuccess(args); - }, - }, - [span([`content for ${args}`])] - ) + }} + > + content for {args} + + ); + }); + return ( +
+ myModal.open('ThingOne')}>Open One + myModal.open('ThingTwo')}>Open Two + {myModal.maybeRender()} +
); - return div([ - h( - ButtonPrimary, - { - onClick: () => myModal.open('ThingOne'), - }, - ['Open One'] - ), - h( - ButtonPrimary, - { - onClick: () => myModal.open('ThingTwo'), - }, - ['Open Two'] - ), - myModal.maybeRender(), - ]); }; it('renders no modal initially', () => { // Arrange // Act - render( - h(TestComponent, { - onCloseThing: () => {}, - onSuccess: () => {}, - }) - ); + render( {}} onSuccess={() => {}} />); // Assert expect(screen.queryAllByText('Modal for ThingOne.').length).toBe(0); @@ -71,12 +56,7 @@ describe('useModalHandler helper hook', () => { it('opens modal with correct args flow', () => { // Arrange - render( - h(TestComponent, { - onCloseThing: () => {}, - onSuccess: () => {}, - }) - ); + render( {}} onSuccess={() => {}} />); // Act const button = screen.getByText('Open One'); @@ -89,12 +69,7 @@ describe('useModalHandler helper hook', () => { it('opens modal with correct args flow - 2nd item', () => { // Arrange - render( - h(TestComponent, { - onCloseThing: () => {}, - onSuccess: () => {}, - }) - ); + render( {}} onSuccess={() => {}} />); // Act const button = screen.getByText('Open Two'); @@ -110,12 +85,7 @@ describe('useModalHandler helper hook', () => { const user = userEvent.setup(); const onSuccessWatcher = jest.fn(); - render( - h(TestComponent, { - onCloseThing: () => {}, - onSuccess: onSuccessWatcher, - }) - ); + render( {}} onSuccess={onSuccessWatcher} />); const button = screen.getByText('Open One'); fireEvent.click(button); @@ -136,12 +106,7 @@ describe('useModalHandler helper hook', () => { const user = userEvent.setup(); const onCloseWatcher = jest.fn(); - render( - h(TestComponent, { - onCloseThing: onCloseWatcher, - onSuccess: () => {}, - }) - ); + render( {}} />); const button = screen.getByText('Open One'); fireEvent.click(button); diff --git a/src/components/useModalHandler.ts b/packages/components/src/useModalHandler.ts similarity index 100% rename from src/components/useModalHandler.ts rename to packages/components/src/useModalHandler.ts diff --git a/src/analysis/Environments/Environments.ts b/src/analysis/Environments/Environments.ts index 7cd5c5bf42..08ae9c5584 100644 --- a/src/analysis/Environments/Environments.ts +++ b/src/analysis/Environments/Environments.ts @@ -1,4 +1,4 @@ -import { PopupTrigger, TooltipTrigger, useThemeFromContext } from '@terra-ui-packages/components'; +import { PopupTrigger, TooltipTrigger, useModalHandler, useThemeFromContext } from '@terra-ui-packages/components'; import { formatDatetime, Mutate, NavLinkProvider } from '@terra-ui-packages/core-utils'; import _ from 'lodash/fp'; import { Fragment, ReactNode, useEffect, useState } from 'react'; @@ -21,7 +21,6 @@ import { icon } from 'src/components/icons'; import { makeMenuIcon } from 'src/components/PopupTrigger'; import SupportRequestWrapper from 'src/components/SupportRequest'; import { SimpleFlexTable, Sortable } from 'src/components/table'; -import { useModalHandler } from 'src/components/useModalHandler'; import { App, isApp } from 'src/libs/ajax/leonardo/models/app-models'; import { PersistentDisk } from 'src/libs/ajax/leonardo/models/disk-models'; import { AzureConfig, GceWithPdConfig, getRegionFromZone } from 'src/libs/ajax/leonardo/models/runtime-config-models'; diff --git a/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSection.ts b/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSection.ts index 31cadf5a7d..1b07281680 100644 --- a/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSection.ts +++ b/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSection.ts @@ -1,6 +1,5 @@ import React from 'react'; import { div } from 'react-hyperscript-helpers'; -import { SingleValue } from 'react-select'; import { AboutPersistentDiskSection } from 'src/analysis/modals/ComputeModal/AboutPersistentDiskSection'; import { AzurePersistentDiskSizeSelectInput } from 'src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSizeSelectInput'; import { PersistentDiskTypeInputContainer } from 'src/analysis/modals/ComputeModal/PersistentDiskTypeInputContainer'; @@ -12,7 +11,7 @@ export interface AzurePersistentDiskSectionProps { persistentDiskSize: number; persistentDiskType: AzurePdType; onChangePersistentDiskType: (type: SharedPdType) => void; - onChangePersistentDiskSize: (size: SingleValue) => void; + onChangePersistentDiskSize: (size: number | null | undefined) => void; onClickAbout: () => void; } diff --git a/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSizeSelectInput.ts b/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSizeSelectInput.ts index 9dff46fb7c..3f50893529 100644 --- a/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSizeSelectInput.ts +++ b/src/analysis/modals/ComputeModal/AzureComputeModal/AzurePersistentDiskSizeSelectInput.ts @@ -1,7 +1,6 @@ import { useUniqueId } from '@terra-ui-packages/components'; import React from 'react'; import { div, h, label } from 'react-hyperscript-helpers'; -import { SingleValue } from 'react-select'; import { IComputeConfig } from 'src/analysis/modal-utils'; import { computeStyles } from 'src/analysis/modals/modalStyles'; import { Select } from 'src/components/common'; @@ -10,7 +9,7 @@ import { defaultAzureDiskSize } from 'src/libs/azure-utils'; export interface AzurePersistentDiskSizeSelectInputProps { persistentDiskSize: number; - onChangePersistentDiskSize: (e: SingleValue) => void; + onChangePersistentDiskSize: (e: number | null | undefined) => void; persistentDiskExists: boolean; } diff --git a/src/analysis/modals/ComputeModal/GcpComputeModal/GcpComputeImageSelect.ts b/src/analysis/modals/ComputeModal/GcpComputeModal/GcpComputeImageSelect.ts index ed89285cc9..1e4074054e 100644 --- a/src/analysis/modals/ComputeModal/GcpComputeModal/GcpComputeImageSelect.ts +++ b/src/analysis/modals/ComputeModal/GcpComputeModal/GcpComputeImageSelect.ts @@ -1,7 +1,6 @@ import _ from 'lodash/fp'; import React from 'react'; import { h } from 'react-hyperscript-helpers'; -import { SingleValue } from 'react-select'; import { ComputeImage } from 'src/analysis/useComputeImages'; import { GroupedSelect } from 'src/components/common'; @@ -66,7 +65,7 @@ export const GcpComputeImageSelect: React.FC = (prop return h(GroupedSelect, { ...restProps, value: selectedComputeImageUrl, - onChange: ({ value }: SingleValue) => { + onChange: ({ value }: any) => { setSelectedComputeImageUrl(value); }, isSearchable: true, diff --git a/src/analysis/modals/ComputeModal/PersistentDiskTypeInput.ts b/src/analysis/modals/ComputeModal/PersistentDiskTypeInput.ts index 45a6c63e52..55513bc001 100644 --- a/src/analysis/modals/ComputeModal/PersistentDiskTypeInput.ts +++ b/src/analysis/modals/ComputeModal/PersistentDiskTypeInput.ts @@ -1,7 +1,6 @@ import { useUniqueId } from '@terra-ui-packages/components'; import React from 'react'; import { div, h, label } from 'react-hyperscript-helpers'; -import { SingleValue } from 'react-select'; import { IComputeConfig } from 'src/analysis/modal-utils'; import { computeStyles } from 'src/analysis/modals/modalStyles'; import { Select } from 'src/components/common'; @@ -9,7 +8,7 @@ import { PdSelectOption, SharedPdType } from 'src/libs/ajax/leonardo/models/disk export interface PersistentDiskTypeInputProps { value: SharedPdType; - onChange: (e: SingleValue<{ value: SharedPdType; label: string | undefined }>) => void; + onChange: (e: { value: SharedPdType; label: string | undefined } | null) => void; isDisabled?: boolean; options: PdSelectOption[]; } diff --git a/src/dataset-builder/CohortEditor.test.ts b/src/dataset-builder/CohortEditor.test.ts index 545731dba4..a000b5d664 100644 --- a/src/dataset-builder/CohortEditor.test.ts +++ b/src/dataset-builder/CohortEditor.test.ts @@ -6,16 +6,18 @@ import { h } from 'react-hyperscript-helpers'; import { AnyCriteria, Cohort, - convertApiDomainOptionToDomainOption, CriteriaGroup, DomainCriteria, - DomainOption, ProgramDataListCriteria, - ProgramDataListOption, ProgramDataRangeCriteria, - ProgramDataRangeOption, } from 'src/dataset-builder/DatasetBuilderUtils'; -import { DataRepo, DataRepoContract } from 'src/libs/ajax/DataRepo'; +import { + DataRepo, + DataRepoContract, + SnapshotBuilderDomainOption, + SnapshotBuilderProgramDataListOption, + SnapshotBuilderProgramDataRangeOption, +} from 'src/libs/ajax/DataRepo'; import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; import { CohortEditor, criteriaFromOption, CriteriaGroupView, CriteriaView } from './CohortEditor'; @@ -62,11 +64,13 @@ describe('CohortEditor', () => { asMockedFn(DataRepo).mockImplementation(() => mockDataRepoContract as DataRepoContract); }; - const programDataRangeOption = (min = 55, max = 99): ProgramDataRangeOption => { + const programDataRangeOption = (min = 55, max = 99): SnapshotBuilderProgramDataRangeOption => { return { id: 0, kind: 'range', name: 'range', + tableName: 'person', + columnName: 'range_column', min, max, }; @@ -137,6 +141,8 @@ describe('CohortEditor', () => { name: 'test name', participantCount: 0, conceptCount: 0, + tableName: 'domain_occurrence', + columnName: 'domain_concept_id', root: { id: 0, name: 'test concept', count: 0, hasChildren: false }, }, }; @@ -153,6 +159,8 @@ describe('CohortEditor', () => { id: 0, name: 'list', kind: 'list', + tableName: 'person', + columnName: 'list_column', values: [], })) as ProgramDataListCriteria; renderCriteriaView({ criteria }); @@ -169,6 +177,8 @@ describe('CohortEditor', () => { id: 0, name: 'list', kind: 'list', + tableName: 'person', + columnName: 'list_column_id', values: [ { id: 0, @@ -207,6 +217,8 @@ describe('CohortEditor', () => { id: 0, name: 'range', kind: 'range', + tableName: 'person', + columnName: 'range_column', min: 55, max: 99, })) as ProgramDataRangeCriteria; @@ -225,6 +237,8 @@ describe('CohortEditor', () => { id: 0, name: 'range', kind: 'range', + tableName: 'person', + columnName: 'range_column', min: 55, max: 99, })) as ProgramDataRangeCriteria; @@ -250,6 +264,8 @@ describe('CohortEditor', () => { const criteria = (await criteriaFromOption(0, { id: 0, + tableName: 'person', + columnName: 'range_column', name: 'range', kind: 'range', min, @@ -275,6 +291,8 @@ describe('CohortEditor', () => { // Arrange const criteria = (await criteriaFromOption(0, { id: 0, + tableName: 'person', + columnName: 'range_column', name: 'range', kind: 'range', min: 55, @@ -293,8 +311,8 @@ describe('CohortEditor', () => { interface ShowCriteriaGroupArgs { initializeGroup?: ((criteriaGroup: CriteriaGroup) => void) | undefined; - domainOptions?: DomainOption[]; - programDataOptions?: (ProgramDataRangeOption | ProgramDataListOption)[]; + domainOptions?: SnapshotBuilderDomainOption[]; + programDataOptions?: (SnapshotBuilderProgramDataRangeOption | SnapshotBuilderProgramDataListOption)[]; } function showCriteriaGroup(args?: ShowCriteriaGroupArgs) { @@ -310,17 +328,20 @@ describe('CohortEditor', () => { } cohort.criteriaGroups.push(criteriaGroup); const updateCohort = jest.fn(); + const datasetDetailsUpdated = _.flow( + _.set('snapshotBuilderSettings.domainOptions', domainOptions), + _.set('snapshotBuilderSettings.programDataOptions', programDataOptions) + )(datasetDetails); + render( h(CriteriaGroupView, { index: 0, criteriaGroup, updateCohort, cohort, - dataset: datasetDetails, + dataset: datasetDetailsUpdated, onStateChange: _.noop, getNextCriteriaIndex, - domainOptions, - programDataOptions, }) ); return { cohort, updateCohort }; @@ -428,7 +449,6 @@ describe('CohortEditor', () => { originalCohort, updateCohorts, getNextCriteriaIndex, - programDataOptions: [], }) ); return { originalCohort, onStateChange, updateCohorts }; @@ -485,14 +505,17 @@ describe('CohortEditor', () => { // Act await user.click(screen.getByText('Add group')); await user.click(screen.getByLabelText('Add criteria')); - const domainOption = convertApiDomainOptionToDomainOption( - datasetDetails!.snapshotBuilderSettings!.domainOptions[0] - ); - const domainMenuItem = screen.getByText(domainOption.name); + const domainMenuItem = screen.getByText(datasetDetails!.snapshotBuilderSettings!.domainOptions[0].name); await user.click(domainMenuItem); // Assert expect(onStateChange).toBeCalledWith( - domainCriteriaSearchState.new(expect.anything(), expect.anything(), _.set('kind', 'domain', domainOption), [], '') + domainCriteriaSearchState.new( + expect.anything(), + expect.anything(), + _.set('kind', 'domain', datasetDetails!.snapshotBuilderSettings!.domainOptions[0]), + [], + '' + ) ); }); }); diff --git a/src/dataset-builder/CohortEditor.ts b/src/dataset-builder/CohortEditor.ts index 9fd6314f02..7e73c505e5 100644 --- a/src/dataset-builder/CohortEditor.ts +++ b/src/dataset-builder/CohortEditor.ts @@ -10,18 +10,22 @@ import { BuilderPageHeader } from 'src/dataset-builder/DatasetBuilderHeader'; import { AnyCriteria, Cohort, - convertApiDomainOptionToDomainOption, CriteriaGroup, DatasetParticipantCountResponse, displayParticipantCount, - DomainOption, ProgramDataListCriteria, - ProgramDataListOption, - ProgramDataListValue, ProgramDataRangeCriteria, - ProgramDataRangeOption, } from 'src/dataset-builder/DatasetBuilderUtils'; -import { DataRepo, DatasetModel } from 'src/libs/ajax/DataRepo'; +import { + DataRepo, + DatasetModel, + SnapshotBuilderDomainOption, + SnapshotBuilderOption, + SnapshotBuilderProgramDataListItem, + SnapshotBuilderProgramDataListOption, + SnapshotBuilderProgramDataOption, + SnapshotBuilderProgramDataRangeOption, +} from 'src/libs/ajax/DataRepo'; import { useLoadedData } from 'src/libs/ajax/loaded-data/useLoadedData'; import colors from 'src/libs/colors'; import * as Utils from 'src/libs/utils'; @@ -111,7 +115,8 @@ export const CriteriaView = (props: CriteriaViewProps) => { _.set( 'values', _.filter( - (value: ProgramDataListValue) => _.flow(_.map('value'), _.includes(value.id))(values), + (value: SnapshotBuilderProgramDataListItem) => + _.flow(_.map('value'), _.includes(value.id))(values), criteria.option.values ) ), @@ -174,11 +179,9 @@ export const CriteriaView = (props: CriteriaViewProps) => { ); }; -type CriteriaOption = DomainOption | ProgramDataListOption | ProgramDataRangeOption; - export const criteriaFromOption = ( index: number, - option: ProgramDataRangeOption | ProgramDataListOption + option: SnapshotBuilderProgramDataRangeOption | SnapshotBuilderProgramDataListOption ): ProgramDataRangeCriteria | ProgramDataListCriteria => { switch (option.kind) { case 'range': { @@ -211,8 +214,6 @@ type AddCriteriaSelectorProps = { onStateChange: OnStateChangeHandler; getNextCriteriaIndex: () => number; cohort: Cohort; - domainOptions: DomainOption[]; - programDataOptions: (ProgramDataListOption | ProgramDataRangeOption)[]; }; const AddCriteriaSelector: React.FC = (props) => { @@ -224,13 +225,22 @@ const AddCriteriaSelector: React.FC = (props) => { onStateChange, getNextCriteriaIndex, cohort, - domainOptions, - programDataOptions, } = props; + const convertToProgramDataOptionSubtype = (option: SnapshotBuilderProgramDataOption) => { + switch (option.kind) { + case 'list': + return option as SnapshotBuilderProgramDataListOption; + case 'range': + return option as SnapshotBuilderProgramDataRangeOption; + default: + throw new Error(`Unknown program data subtype: ${option.kind}`); + } + }; + return ( snapshotBuilderSettings && - h(GroupedSelect, { + h(GroupedSelect, { styles: { container: (provided) => ({ ...provided, width: '230px', marginTop: wideMargin }) }, isClearable: false, isSearchable: false, @@ -242,7 +252,7 @@ const AddCriteriaSelector: React.FC = (props) => { value: domainOption, label: domainOption.name, }), - domainOptions + snapshotBuilderSettings.domainOptions ), }, { @@ -252,7 +262,7 @@ const AddCriteriaSelector: React.FC = (props) => { value: programDataOption, label: programDataOption.name, }; - }, programDataOptions), + }, snapshotBuilderSettings.programDataOptions), }, ], 'aria-label': addCriteriaText, @@ -261,13 +271,18 @@ const AddCriteriaSelector: React.FC = (props) => { onChange: async (criteriaOption) => { if (criteriaOption !== null) { if (criteriaOption.value.kind === 'domain') { - onStateChange(domainCriteriaSearchState.new(cohort, criteriaGroup, criteriaOption.value)); + onStateChange( + domainCriteriaSearchState.new(cohort, criteriaGroup, criteriaOption.value as SnapshotBuilderDomainOption) + ); } else { const criteriaIndex = getNextCriteriaIndex(); updateCohort( _.set( `criteriaGroups.${index}.criteria.${criteriaGroup.criteria.length}`, - criteriaFromOption(criteriaIndex, criteriaOption.value) + criteriaFromOption( + criteriaIndex, + convertToProgramDataOptionSubtype(criteriaOption.value as SnapshotBuilderProgramDataOption) + ) ) ); } @@ -285,22 +300,10 @@ type CriteriaGroupViewProps = { dataset: DatasetModel; onStateChange: OnStateChangeHandler; getNextCriteriaIndex: () => number; - domainOptions: DomainOption[]; - programDataOptions: (ProgramDataListOption | ProgramDataRangeOption)[]; }; export const CriteriaGroupView: React.FC = (props) => { - const { - index, - criteriaGroup, - updateCohort, - cohort, - dataset, - onStateChange, - getNextCriteriaIndex, - programDataOptions, - domainOptions, - } = props; + const { index, criteriaGroup, updateCohort, cohort, dataset, onStateChange, getNextCriteriaIndex } = props; const deleteCriteria = (criteria: AnyCriteria) => updateCohort(_.set(`criteriaGroups.${index}.criteria`, _.without([criteria], criteriaGroup.criteria))); @@ -407,8 +410,6 @@ export const CriteriaGroupView: React.FC = (props) => { onStateChange, getNextCriteriaIndex, cohort, - programDataOptions, - domainOptions, }), ]), div( @@ -442,12 +443,9 @@ type CohortGroupsProps = { updateCohort: Updater; onStateChange: OnStateChangeHandler; getNextCriteriaIndex: () => number; - domainOptions: DomainOption[]; - programDataOptions: (ProgramDataListOption | ProgramDataRangeOption)[]; }; const CohortGroups: React.FC = (props) => { - const { dataset, cohort, updateCohort, onStateChange, getNextCriteriaIndex, domainOptions, programDataOptions } = - props; + const { dataset, cohort, updateCohort, onStateChange, getNextCriteriaIndex } = props; return div({ style: { width: '47rem' } }, [ cohort == null ? 'No cohort found' @@ -463,8 +461,6 @@ const CohortGroups: React.FC = (props) => { dataset, onStateChange, getNextCriteriaIndex, - domainOptions, - programDataOptions, }), div({ style: { marginTop: '1rem', display: 'flex', alignItems: 'center' } }, [ div( @@ -502,12 +498,9 @@ type CohortEditorContentsProps = { dataset: DatasetModel; onStateChange: OnStateChangeHandler; getNextCriteriaIndex: () => number; - domainOptions: DomainOption[]; - programDataOptions: (ProgramDataListOption | ProgramDataRangeOption)[]; }; const CohortEditorContents: React.FC = (props) => { - const { updateCohort, cohort, dataset, onStateChange, getNextCriteriaIndex, domainOptions, programDataOptions } = - props; + const { updateCohort, cohort, dataset, onStateChange, getNextCriteriaIndex } = props; return h(BuilderPageHeader, [ h2({ style: { display: 'flex', alignItems: 'center' } }, [ h( @@ -531,8 +524,6 @@ const CohortEditorContents: React.FC = (props) => { updateCohort, onStateChange, getNextCriteriaIndex, - domainOptions, - programDataOptions, }), h( ButtonOutline, @@ -556,18 +547,12 @@ interface CohortEditorProps { readonly originalCohort: Cohort; readonly updateCohorts: Updater; readonly getNextCriteriaIndex: () => number; - readonly programDataOptions: (ProgramDataListOption | ProgramDataRangeOption)[]; } export const CohortEditor: React.FC = (props) => { - const { onStateChange, dataset, originalCohort, updateCohorts, getNextCriteriaIndex, programDataOptions } = props; + const { onStateChange, dataset, originalCohort, updateCohorts, getNextCriteriaIndex } = props; const [cohort, setCohort] = useState(originalCohort); - // Program data options passed in because we want to cache them per dataset - const domainOptions = _.map( - (snapshotBuilderDomainOption) => convertApiDomainOptionToDomainOption(snapshotBuilderDomainOption), - dataset?.snapshotBuilderSettings?.domainOptions - ); const updateCohort = (updateCohort: (Cohort) => Cohort) => setCohort(updateCohort); return h(Fragment, [ @@ -577,8 +562,6 @@ export const CohortEditor: React.FC = (props) => { dataset, onStateChange, getNextCriteriaIndex, - domainOptions, - programDataOptions, }), // add div to cover page to footer div( diff --git a/src/dataset-builder/ConceptSearch.test.ts b/src/dataset-builder/ConceptSearch.test.ts index 36913c4332..2ccd5bde98 100644 --- a/src/dataset-builder/ConceptSearch.test.ts +++ b/src/dataset-builder/ConceptSearch.test.ts @@ -4,7 +4,6 @@ import _ from 'lodash/fp'; import { act } from 'react-dom/test-utils'; import { h } from 'react-hyperscript-helpers'; import { ConceptSearch } from 'src/dataset-builder/ConceptSearch'; -import { convertApiDomainOptionToDomainOption } from 'src/dataset-builder/DatasetBuilderUtils'; import { dummyDatasetModel, dummyGetConceptForId } from 'src/dataset-builder/TestConstants'; import { DataRepo, DataRepoContract, SnapshotBuilderConcept } from 'src/libs/ajax/DataRepo'; import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; @@ -31,9 +30,7 @@ describe('ConceptSearch', () => { const onOpenHierarchy = jest.fn(); const actionText = 'action text'; const datasetId = '0'; - const domainOption = convertApiDomainOptionToDomainOption( - dummyDatasetModel()!.snapshotBuilderSettings!.domainOptions[0] - ); + const domainOption = dummyDatasetModel()!.snapshotBuilderSettings!.domainOptions[0]; const renderSearch = (initialSearch = '', initialCart: SnapshotBuilderConcept[] = []) => render( diff --git a/src/dataset-builder/ConceptSearch.ts b/src/dataset-builder/ConceptSearch.ts index 3eb3c9f058..8d92de3e7e 100644 --- a/src/dataset-builder/ConceptSearch.ts +++ b/src/dataset-builder/ConceptSearch.ts @@ -9,18 +9,18 @@ import { TextInput, withDebouncedChange } from 'src/components/input'; import { SimpleTable } from 'src/components/table'; import { tableHeaderStyle } from 'src/dataset-builder/ConceptSelector'; import { BuilderPageHeader } from 'src/dataset-builder/DatasetBuilderHeader'; -import { DomainOption, GetConceptsResponse, HighlightConceptName } from 'src/dataset-builder/DatasetBuilderUtils'; -import { DataRepo, SnapshotBuilderConcept as Concept } from 'src/libs/ajax/DataRepo'; +import { GetConceptsResponse, HighlightConceptName } from 'src/dataset-builder/DatasetBuilderUtils'; +import { DataRepo, SnapshotBuilderConcept as Concept, SnapshotBuilderDomainOption } from 'src/libs/ajax/DataRepo'; import { useLoadedData } from 'src/libs/ajax/loaded-data/useLoadedData'; import colors from 'src/libs/colors'; type ConceptSearchProps = { readonly initialSearch: string; - readonly domainOption: DomainOption; + readonly domainOption: SnapshotBuilderDomainOption; readonly onCancel: () => void; readonly onCommit: (selected: Concept[]) => void; readonly onOpenHierarchy: ( - domainOption: DomainOption, + domainOption: SnapshotBuilderDomainOption, cart: Concept[], searchText: string, openedConcept?: Concept diff --git a/src/dataset-builder/DatasetBuilder.ts b/src/dataset-builder/DatasetBuilder.ts index f57861c935..3cc6baa51f 100644 --- a/src/dataset-builder/DatasetBuilder.ts +++ b/src/dataset-builder/DatasetBuilder.ts @@ -18,8 +18,6 @@ import { DatasetBuilderValue, DatasetParticipantCountResponse, displayParticipantCount, - ProgramDataListOption, - ProgramDataRangeOption, } from 'src/dataset-builder/DatasetBuilderUtils'; import { DomainCriteriaSearch } from 'src/dataset-builder/DomainCriteriaSearch'; import { @@ -685,33 +683,19 @@ export const DatasetBuilderView: React.FC = (props) => { const [conceptSets, setConceptSets] = useState([]); const onStateChange = setDatasetBuilderState; - const [programDataOptions, loadProgramDataOptions] = - useLoadedData<(ProgramDataRangeOption | ProgramDataListOption)[]>(); - const getNextCriteriaIndex = () => { criteriaCount++; return criteriaCount; }; - const loadDatasetProgramDataOptions = (dataset) => - Promise.all( - _.map( - (snapshotBuilderProgramDataOption) => - DataRepo().dataset(dataset.id).queryDatasetColumnStatisticsById(snapshotBuilderProgramDataOption), - dataset?.snapshotBuilderSettings?.programDataOptions - ) - ); - useOnMount(() => { - void loadDatasetModel(async () => { - const dataset = await DataRepo() + void loadDatasetModel(async () => + DataRepo() .dataset(datasetId) - .details([datasetIncludeTypes.SNAPSHOT_BUILDER_SETTINGS, datasetIncludeTypes.PROPERTIES]); - void loadProgramDataOptions(() => loadDatasetProgramDataOptions(dataset)); - return dataset; - }); + .details([datasetIncludeTypes.SNAPSHOT_BUILDER_SETTINGS, datasetIncludeTypes.PROPERTIES]) + ); }); - return datasetModel.status === 'Ready' && programDataOptions.status === 'Ready' + return datasetModel.status === 'Ready' ? h(FooterWrapper, [ h(TopBar, { title: 'Preview', href: '' }, []), h(DatasetBuilderHeader, { datasetDetails: datasetModel.state }), @@ -735,7 +719,6 @@ export const DatasetBuilderView: React.FC = (props) => { dataset: datasetModel.state, updateCohorts: setCohorts, getNextCriteriaIndex, - programDataOptions: programDataOptions.status === 'Ready' ? programDataOptions.state : [], }) : div(['No Dataset Builder Settings Found']); case 'domain-criteria-selector': diff --git a/src/dataset-builder/DatasetBuilderDetails.ts b/src/dataset-builder/DatasetBuilderDetails.ts index ea7d56984f..22bb631851 100644 --- a/src/dataset-builder/DatasetBuilderDetails.ts +++ b/src/dataset-builder/DatasetBuilderDetails.ts @@ -16,7 +16,7 @@ import { DatasetBuilderBreadcrumbs } from './Breadcrumbs'; interface DomainDisplayProps { title: string; displayInformation: { - category: string; + name: string; participantCount?: number; conceptCount?: number; }[]; @@ -40,10 +40,10 @@ const TileDisplay = (props: DomainDisplayProps) => { marginRight: '1rem', border: `1px solid ${colors.light()}`, }, - key: displayTile.category, + key: displayTile.name, }, [ - h3([displayTile.category]), + h3([displayTile.name]), div({ style: { display: 'flex', alignItems: 'baseline' } }, [ div({ style: { fontSize: 30, fontWeight: 600 } }, [ displayTile.conceptCount ? `${displayTile.conceptCount / 1000}K` : 'UNKNOWN', diff --git a/src/dataset-builder/DatasetBuilderUtils.test.ts b/src/dataset-builder/DatasetBuilderUtils.test.ts index e30385531e..3429cde26b 100644 --- a/src/dataset-builder/DatasetBuilderUtils.test.ts +++ b/src/dataset-builder/DatasetBuilderUtils.test.ts @@ -15,19 +15,21 @@ import { DatasetAccessRequestApi, DomainCriteria, DomainCriteriaApi, - DomainOption, HighlightConceptName, ProgramDataListCriteria, ProgramDataListCriteriaApi, - ProgramDataListOption, - ProgramDataListValue, ProgramDataRangeCriteria, ProgramDataRangeCriteriaApi, - ProgramDataRangeOption, ValueSet, ValueSetApi, } from 'src/dataset-builder/DatasetBuilderUtils'; -import { SnapshotBuilderConcept } from 'src/libs/ajax/DataRepo'; +import { + SnapshotBuilderConcept, + SnapshotBuilderDomainOption, + SnapshotBuilderProgramDataListItem, + SnapshotBuilderProgramDataListOption, + SnapshotBuilderProgramDataRangeOption, +} from 'src/libs/ajax/DataRepo'; const concept: SnapshotBuilderConcept = { id: 0, @@ -36,10 +38,12 @@ const concept: SnapshotBuilderConcept = { hasChildren: false, }; -const domainOption: DomainOption = { +const domainOption: SnapshotBuilderDomainOption = { id: 1, name: 'category', kind: 'domain', + tableName: 'category_occurrence', + columnName: 'category_concept_id', conceptCount: 10, participantCount: 20, root: concept, @@ -61,9 +65,11 @@ const domainCriteriaApi: DomainCriteriaApi = { conceptId: 100, }; -const rangeOption: ProgramDataRangeOption = { +const rangeOption: SnapshotBuilderProgramDataRangeOption = { id: 2, kind: 'range', + tableName: 'person', + columnName: 'range_column', min: 0, max: 101, name: 'rangeOption', @@ -86,16 +92,18 @@ const rangeCriteriaApi: ProgramDataRangeCriteriaApi = { high: 99, }; -const optionValues: ProgramDataListValue[] = [{ id: 5, name: 'listOptionListValue' }]; +const optionValues: SnapshotBuilderProgramDataListItem[] = [{ id: 5, name: 'listOptionListValue' }]; -const listOption: ProgramDataListOption = { +const listOption: SnapshotBuilderProgramDataListOption = { id: 2, kind: 'list', name: 'listOption', + tableName: 'person', + columnName: 'list_concept_id', values: optionValues, }; -const criteriaListValues: ProgramDataListValue[] = [ +const criteriaListValues: SnapshotBuilderProgramDataListItem[] = [ { id: 7, name: 'criteriaListValue1' }, { id: 8, name: 'criteriaListValue2' }, ]; diff --git a/src/dataset-builder/DatasetBuilderUtils.ts b/src/dataset-builder/DatasetBuilderUtils.ts index 730805bae5..e52668349e 100644 --- a/src/dataset-builder/DatasetBuilderUtils.ts +++ b/src/dataset-builder/DatasetBuilderUtils.ts @@ -2,12 +2,13 @@ import _ from 'lodash/fp'; import { ReactElement } from 'react'; import { div, span } from 'react-hyperscript-helpers'; import { - ColumnStatisticsIntOrDoubleModel, - ColumnStatisticsTextModel, SnapshotBuilderConcept as Concept, - SnapshotBuilderConcept, SnapshotBuilderDomainOption, - SnapshotBuilderProgramDataOption, + SnapshotBuilderOption, + SnapshotBuilderOptionTypeNames, + SnapshotBuilderProgramDataListItem, + SnapshotBuilderProgramDataListOption, + SnapshotBuilderProgramDataRangeOption, } from 'src/libs/ajax/DataRepo'; /** A specific criteria based on a type. */ @@ -15,8 +16,8 @@ export interface Criteria { index: number; count?: number; // The kind is duplicated to make use of the discriminator type - kind: OptionTypeNames; - option: Option; + kind: SnapshotBuilderOptionTypeNames; + option: SnapshotBuilderOption; } /** API types represent the data of UI types in the format expected by the backend. @@ -25,7 +26,7 @@ export interface Criteria { export interface CriteriaApi { // This is the ID for either the domain or the program data option id: number; - kind: OptionTypeNames; + kind: SnapshotBuilderOptionTypeNames; name: string; count?: number; } @@ -78,56 +79,24 @@ export type DatasetAccessRequestApi = { }; /** Below are the UI types */ - -type OptionTypeNames = 'domain' | 'range' | 'list'; - -export interface Option { - id: number; - name: string; - kind: OptionTypeNames; -} - -export interface ProgramDataRangeOption extends Option { - kind: 'range'; - min: number; - max: number; -} - -export interface DomainOption extends Option { - kind: 'domain'; - conceptCount?: number; - participantCount?: number; - root: SnapshotBuilderConcept; -} - export interface DomainCriteria extends Criteria { kind: 'domain'; conceptId: number; conceptName: string; - option: DomainOption; + option: SnapshotBuilderDomainOption; } export interface ProgramDataRangeCriteria extends Criteria { kind: 'range'; - option: ProgramDataRangeOption; + option: SnapshotBuilderProgramDataRangeOption; low: number; high: number; } -export interface ProgramDataListValue { - id: number; - name: string; -} - -export interface ProgramDataListOption extends Option { - kind: 'list'; - values: ProgramDataListValue[]; -} - export interface ProgramDataListCriteria extends Criteria { kind: 'list'; - option: ProgramDataListOption; - values: ProgramDataListValue[]; + option: SnapshotBuilderProgramDataListOption; + values: SnapshotBuilderProgramDataListItem[]; } export type AnyCriteria = DomainCriteria | ProgramDataRangeCriteria | ProgramDataListCriteria; @@ -245,53 +214,9 @@ export type DatasetParticipantCountResponse = { sql: string; }; -export const convertApiDomainOptionToDomainOption = (domainOption: SnapshotBuilderDomainOption): DomainOption => ({ - ...domainOption, - name: domainOption.category, - kind: 'domain', -}); - export const convertDatasetParticipantCountRequest = (request: DatasetParticipantCountRequest) => { return { cohorts: _.map(convertCohort, request.cohorts) }; }; -export const convertProgramDataOptionToListOption = ( - programDataOption: SnapshotBuilderProgramDataOption -): ProgramDataListOption => ({ - id: programDataOption.id, - name: programDataOption.name, - kind: 'list', - values: _.map( - (num) => ({ - id: num, - name: `${programDataOption.name} ${num}`, - }), - [0, 1, 2, 3, 4] - ), -}); - -export const convertProgramDataOptionToRangeOption = ( - programDataOption: SnapshotBuilderProgramDataOption, - statistics: ColumnStatisticsIntOrDoubleModel | ColumnStatisticsTextModel -): ProgramDataRangeOption => { - switch (statistics.dataType) { - case 'float': - case 'float64': - case 'integer': - case 'int64': - case 'numeric': - return { - name: programDataOption.name, - id: programDataOption.id, - kind: 'range', - min: statistics.minValue, - max: statistics.maxValue, - }; - default: - throw new Error( - `Datatype ${statistics.dataType} for ${programDataOption.tableName}/${programDataOption.columnName} is not numeric` - ); - } -}; export const HighlightConceptName = ({ conceptName, searchFilter }): ReactElement => { const startIndex = conceptName.toLowerCase().indexOf(searchFilter.toLowerCase()); diff --git a/src/dataset-builder/DomainCriteriaSelector.test.ts b/src/dataset-builder/DomainCriteriaSelector.test.ts index 8e34d55e3f..67bd0fd4f5 100644 --- a/src/dataset-builder/DomainCriteriaSelector.test.ts +++ b/src/dataset-builder/DomainCriteriaSelector.test.ts @@ -2,7 +2,6 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import _ from 'lodash/fp'; import { h } from 'react-hyperscript-helpers'; -import { convertApiDomainOptionToDomainOption } from 'src/dataset-builder/DatasetBuilderUtils'; import { DataRepo, DataRepoContract } from 'src/libs/ajax/DataRepo'; import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; @@ -36,9 +35,7 @@ describe('DomainCriteriaSelector', () => { const datasetId = ''; const concept = dummyGetConceptForId(101); const children = [dummyGetConceptForId(102)]; - const domainOption = convertApiDomainOptionToDomainOption( - dummyDatasetModel()!.snapshotBuilderSettings!.domainOptions[0] - ); + const domainOption = dummyDatasetModel()!.snapshotBuilderSettings!.domainOptions[0]; const cohort = newCohort('cohort'); cohort.criteriaGroups.push(newCriteriaGroup()); asMockedFn(DataRepo).mockImplementation(() => mockDataRepoContract as DataRepoContract); diff --git a/src/dataset-builder/DomainCriteriaSelector.ts b/src/dataset-builder/DomainCriteriaSelector.ts index b1235d3db4..bdb1a61d9a 100644 --- a/src/dataset-builder/DomainCriteriaSelector.ts +++ b/src/dataset-builder/DomainCriteriaSelector.ts @@ -1,8 +1,13 @@ import _ from 'lodash/fp'; import { h } from 'react-hyperscript-helpers'; import { spinnerOverlay } from 'src/components/common'; -import { DomainCriteria, DomainOption } from 'src/dataset-builder/DatasetBuilderUtils'; -import { DataRepo, SnapshotBuilderConcept as Concept, SnapshotBuilderConcept } from 'src/libs/ajax/DataRepo'; +import { DomainCriteria } from 'src/dataset-builder/DatasetBuilderUtils'; +import { + DataRepo, + SnapshotBuilderConcept as Concept, + SnapshotBuilderConcept, + SnapshotBuilderDomainOption, +} from 'src/libs/ajax/DataRepo'; import { useLoadedData } from 'src/libs/ajax/loaded-data/useLoadedData'; import { useOnMount } from 'src/libs/react-utils'; @@ -23,7 +28,7 @@ interface DomainCriteriaSelectorProps { } export const toCriteria = - (domainOption: DomainOption, getNextCriteriaIndex: () => number) => + (domainOption: SnapshotBuilderDomainOption, getNextCriteriaIndex: () => number) => (concept: Concept): DomainCriteria => { return { kind: 'domain', diff --git a/src/dataset-builder/TestConstants.ts b/src/dataset-builder/TestConstants.ts index 371218f7e1..c047301911 100644 --- a/src/dataset-builder/TestConstants.ts +++ b/src/dataset-builder/TestConstants.ts @@ -16,6 +16,8 @@ export const dummyDatasetModel = (): DatasetModel => ({ kind: 'range', tableName: 'person', columnName: 'year_of_birth', + min: 1907, + max: 2013, }, { id: 1, @@ -23,6 +25,10 @@ export const dummyDatasetModel = (): DatasetModel => ({ kind: 'list', tableName: 'person', columnName: 'ethnicity', + values: [ + { name: 'ethnicity 1', id: 300 }, + { name: 'ethnicity 2', id: 301 }, + ], }, { id: 2, @@ -30,6 +36,10 @@ export const dummyDatasetModel = (): DatasetModel => ({ kind: 'list', tableName: 'person', columnName: 'gender_identity', + values: [ + { name: 'gender 1', id: 200 }, + { name: 'gender 2', id: 201 }, + ], }, { id: 3, @@ -37,26 +47,39 @@ export const dummyDatasetModel = (): DatasetModel => ({ kind: 'list', tableName: 'person', columnName: 'race', + values: [ + { name: 'race 1', id: 100 }, + { name: 'race 2', id: 101 }, + ], }, ], domainOptions: [ { + kind: 'domain', id: 10, - category: 'Condition', + name: 'Condition', + tableName: 'condition_occurrence', + columnName: 'condition_concept_id', conceptCount: 18000, participantCount: 12500, root: dummyGetConceptForId(100), }, { + kind: 'domain', id: 11, - category: 'Procedure', + name: 'Procedure', + tableName: 'procedure_occurrence', + columnName: 'procedure_concept_id', conceptCount: 22500, participantCount: 11328, root: dummyGetConceptForId(200), }, { + kind: 'domain', id: 12, - category: 'Observation', + name: 'Observation', + tableName: 'observation', + columnName: 'observation_concept_id', conceptCount: 12300, participantCount: 23223, root: dummyGetConceptForId(300), diff --git a/src/dataset-builder/dataset-builder-types.ts b/src/dataset-builder/dataset-builder-types.ts index bf12b85b82..f313fcc555 100644 --- a/src/dataset-builder/dataset-builder-types.ts +++ b/src/dataset-builder/dataset-builder-types.ts @@ -1,5 +1,5 @@ -import { Cohort, CriteriaGroup, DomainOption } from 'src/dataset-builder/DatasetBuilderUtils'; -import { SnapshotBuilderConcept as Concept } from 'src/libs/ajax/DataRepo'; +import { Cohort, CriteriaGroup } from 'src/dataset-builder/DatasetBuilderUtils'; +import { SnapshotBuilderConcept as Concept, SnapshotBuilderDomainOption } from 'src/libs/ajax/DataRepo'; let groupCount = 1; export const newCriteriaGroup = (): CriteriaGroup => { @@ -59,7 +59,7 @@ export interface DomainCriteriaSelectorState extends DatasetBuilderState { readonly cohort: Cohort; readonly criteriaGroup: CriteriaGroup; - readonly domainOption: DomainOption; + readonly domainOption: SnapshotBuilderDomainOption; readonly cart: Concept[]; readonly cancelState: AnyDatasetBuilderState; readonly openedConcept?: Concept; @@ -69,7 +69,7 @@ export const domainCriteriaSelectorState = { new: ( cohort: Cohort, criteriaGroup: CriteriaGroup, - domainOption: DomainOption, + domainOption: SnapshotBuilderDomainOption, cart: Concept[], cancelState: AnyDatasetBuilderState, openedConcept?: Concept @@ -89,7 +89,7 @@ export interface DomainCriteriaSearchState extends DatasetBuilderState { readonly cohort: Cohort; readonly criteriaGroup: CriteriaGroup; - readonly domainOption: DomainOption; + readonly domainOption: SnapshotBuilderDomainOption; readonly cart: Concept[]; readonly searchText: string; } @@ -98,7 +98,7 @@ export const domainCriteriaSearchState = { new: ( cohort: Cohort, criteriaGroup: CriteriaGroup, - domainOption: DomainOption, + domainOption: SnapshotBuilderDomainOption, cart: Concept[] = [], searchText = '' ): DomainCriteriaSearchState => ({ diff --git a/src/libs/ajax/DataRepo.ts b/src/libs/ajax/DataRepo.ts index 1f67c1c505..6f9b250c68 100644 --- a/src/libs/ajax/DataRepo.ts +++ b/src/libs/ajax/DataRepo.ts @@ -2,16 +2,12 @@ import * as _ from 'lodash/fp'; import { convertDatasetAccessRequest, convertDatasetParticipantCountRequest, - convertProgramDataOptionToListOption, - convertProgramDataOptionToRangeOption, DatasetAccessRequest, DatasetAccessRequestApi, DatasetParticipantCountRequest, DatasetParticipantCountResponse, GetConceptHierarchyResponse, GetConceptsResponse, - ProgramDataListOption, - ProgramDataRangeOption, SearchConceptsResponse, } from 'src/dataset-builder/DatasetBuilderUtils'; import { authOpts, fetchDataRepo, jsonBody } from 'src/libs/ajax/ajax-common'; @@ -24,22 +20,43 @@ export type SnapshotBuilderConcept = { children?: SnapshotBuilderConcept[]; }; -export type SnapshotBuilderDomainOption = { - id: number; - category: string; - conceptCount?: number; - participantCount?: number; - root: SnapshotBuilderConcept; -}; +export type SnapshotBuilderOptionTypeNames = 'list' | 'range' | 'domain'; -export interface SnapshotBuilderProgramDataOption { - id: number; - kind: 'range' | 'list'; +export interface SnapshotBuilderOption { + kind: SnapshotBuilderOptionTypeNames; name: string; + id: number; tableName: string; columnName: string; } +export interface SnapshotBuilderProgramDataOption extends SnapshotBuilderOption { + kind: 'range' | 'list'; +} + +export interface SnapshotBuilderProgramDataListItem { + name: string; + id: number; +} + +export interface SnapshotBuilderProgramDataListOption extends SnapshotBuilderProgramDataOption { + kind: 'list'; + values: SnapshotBuilderProgramDataListItem[]; +} + +export interface SnapshotBuilderProgramDataRangeOption extends SnapshotBuilderProgramDataOption { + kind: 'range'; + min: number; + max: number; +} + +export interface SnapshotBuilderDomainOption extends SnapshotBuilderOption { + kind: 'domain'; + conceptCount?: number; + participantCount?: number; + root: SnapshotBuilderConcept; +} + export type SnapshotBuilderFeatureValueGroup = { id: number; name: string; @@ -53,7 +70,7 @@ export type SnapshotBuilderDatasetConceptSets = { export type SnapshotBuilderSettings = { domainOptions: SnapshotBuilderDomainOption[]; - programDataOptions: SnapshotBuilderProgramDataOption[]; + programDataOptions: (SnapshotBuilderProgramDataListOption | SnapshotBuilderProgramDataRangeOption)[]; featureValueGroups: SnapshotBuilderFeatureValueGroup[]; datasetConceptSets?: SnapshotBuilderDatasetConceptSets[]; }; @@ -101,42 +118,6 @@ export interface Snapshot { cloudPlatform: 'azure' | 'gcp'; } -export interface ColumnStatisticsModel { - dataType: - | 'string' - | 'boolean' - | 'bytes' - | 'date' - | 'datetime' - | 'dirref' - | 'fileref' - | 'float' - | 'float64' - | 'integer' - | 'int64' - | 'numeric' - | 'record' - | 'text' - | 'time' - | 'timestamp'; -} - -export interface ColumnStatisticsIntOrDoubleModel extends ColumnStatisticsModel { - dataType: 'float' | 'float64' | 'integer' | 'int64' | 'numeric'; - minValue: number; - maxValue: number; -} - -export interface ColumnStatisticsTextModel extends ColumnStatisticsModel { - dataType: 'string' | 'text'; - values: ColumnStatisticsTextValue[]; -} - -interface ColumnStatisticsTextValue { - value: string; - count: number; -} - export type JobStatus = 'running' | 'succeeded' | 'failed'; export const jobStatusTypes: Record = { @@ -159,9 +140,6 @@ export interface DataRepoContract { dataset: (datasetId: string) => { details: (include?: DatasetInclude[]) => Promise; roles: () => Promise; - queryDatasetColumnStatisticsById: ( - dataOption: SnapshotBuilderProgramDataOption - ) => Promise; createSnapshotRequest(request: DatasetAccessRequest): Promise; getCounts(request: DatasetParticipantCountRequest): Promise; getConcepts(parent: SnapshotBuilderConcept): Promise; @@ -191,26 +169,6 @@ const callDataRepoPost = async (url: string, signal: AbortSignal | undefined, js return await res.json(); }; -const handleProgramDataOptions = async ( - datasetId: string, - programDataOption: SnapshotBuilderProgramDataOption, - signal: AbortSignal | undefined -): Promise => { - switch (programDataOption.kind) { - case 'list': - return convertProgramDataOptionToListOption(programDataOption); - case 'range': { - const statistics: ColumnStatisticsTextModel | ColumnStatisticsIntOrDoubleModel = await callDataRepoPost( - `repository/v1/datasets/${datasetId}/data/${programDataOption.tableName}/statistics/${programDataOption.columnName}`, - signal, - {} - ); - return convertProgramDataOptionToRangeOption(programDataOption, statistics); - } - default: - throw new Error('Unexpected option'); - } -}; export const DataRepo = (signal?: AbortSignal): DataRepoContract => ({ dataset: (datasetId) => { return { @@ -240,8 +198,6 @@ export const DataRepo = (signal?: AbortSignal): DataRepoContract => ({ }, getConceptHierarchy: async (concept: SnapshotBuilderConcept) => callDataRepo(`repository/v1/datasets/${datasetId}/snapshotBuilder/conceptHierarchy/${concept.id}`), - queryDatasetColumnStatisticsById: (programDataOption) => - handleProgramDataOptions(datasetId, programDataOption, signal), }; }, snapshot: (snapshotId) => { diff --git a/yarn.lock b/yarn.lock index 10aec25c1f..c9301439f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16896,7 +16896,6 @@ __metadata: react-notifications-component: ^4.0.1 react-oidc-context: ^2.1.0 react-paginating: ^1.4.0 - react-select: ^5.7.4 react-simplemde-editor: ^5.0.2 react-textarea-autosize: ^8.5.2 react-transition-group: ^4.4.5