From d0feb41efc225b0816bcbc57f8a8a8b3d44efe05 Mon Sep 17 00:00:00 2001 From: Sky Rubenstein Date: Wed, 13 Mar 2024 14:52:40 -0400 Subject: [PATCH] [DC-876] Update to use new SnapshotBuilderSettings (#4707) --- src/dataset-builder/CohortEditor.test.ts | 57 ++++++--- src/dataset-builder/CohortEditor.ts | 93 ++++++--------- src/dataset-builder/ConceptSearch.test.ts | 5 +- src/dataset-builder/ConceptSearch.ts | 8 +- src/dataset-builder/DatasetBuilder.ts | 27 +---- src/dataset-builder/DatasetBuilderDetails.ts | 6 +- .../DatasetBuilderUtils.test.ts | 28 +++-- src/dataset-builder/DatasetBuilderUtils.ts | 99 ++-------------- .../DomainCriteriaSelector.test.ts | 5 +- src/dataset-builder/DomainCriteriaSelector.ts | 11 +- src/dataset-builder/TestConstants.ts | 29 ++++- src/dataset-builder/dataset-builder-types.ts | 12 +- src/libs/ajax/DataRepo.ts | 108 ++++++------------ 13 files changed, 194 insertions(+), 294 deletions(-) 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) => {