Skip to content

Commit

Permalink
Merge branch 'feat/vadc_sprint21' into bugfix/comboBoxLabels
Browse files Browse the repository at this point in the history
  • Loading branch information
jarvisraymond-uchicago authored Oct 23, 2024
2 parents 10881d9 + 7d03acf commit e60c8be
Show file tree
Hide file tree
Showing 19 changed files with 470 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import './AttritionTable.css';
const { Panel } = Collapse;

const AttritionTable = ({
selectedCohort, outcome, covariates, tableType,
selectedCohort, outcome, covariates, tableType, modalInfo, setModalInfo,
}) => {
const [covariatesProcessed, setCovariatesProcessed] = useState([]);
// Creates an array of arrays such that given input arr [A,B,C]
Expand Down Expand Up @@ -109,6 +109,8 @@ const AttritionTable = ({
currentCovariateAndCovariatesFromPrecedingRows={[
applyAutoGenFilters(),
]}
modalInfo={modalInfo}
setModalInfo={setModalInfo}
/>
</React.Fragment>
)}
Expand All @@ -129,6 +131,8 @@ const AttritionTable = ({
...item,
applyAutoGenFilters(),
]}
modalInfo={modalInfo}
setModalInfo={setModalInfo}
/>
</React.Fragment>
))
Expand All @@ -146,6 +150,8 @@ AttritionTable.propTypes = {
outcome: PropTypes.object,
covariates: PropTypes.array,
tableType: PropTypes.string.isRequired,
modalInfo: PropTypes.object.isRequired,
setModalInfo: PropTypes.func.isRequired,
};

AttritionTable.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { rest } from 'msw';
import AttritionTable from './AttritionTable';
import { SourceContextProvider } from '../../../Utils/Source';
import {
generateEulerTestData,
generateHistogramTestData,
} from '../../../TestData/generateDiagramTestData';
import '../../../GWASApp.css';

export default {
Expand All @@ -15,18 +19,19 @@ const mockedQueryClient = new QueryClient();
const MockTemplate = () => {
const [covariateArrSizeTable1, setCovariateArrSizeTable1] = useState(10);
const [covariateArrSizeTable2, setCovariateArrSizeTable2] = useState(2);

const selectedCohort = {
size: 123,
cohort_definition_id: 123,
cohort_name: 'cohort name abc',
};

const outcome = {
size: 123,
variable_type: 'custom_dichotomous',
cohort_sizes: [10000, 20000],
cohort_names: ['name1', 'name2'],
cohort_ids: [1, 2],
provided_name: 'dichotomous test1',
};

const covariatesArrFirstTable = Array.from(
{ length: covariateArrSizeTable1 },
(_, i) => ({
Expand All @@ -41,6 +46,8 @@ const MockTemplate = () => {
(_, i) => ({
variable_type: 'custom_dichotomous',
provided_name: 'providednamebyuser' + i,
cohort_sizes: [10000, 20000],
cohort_names: ['name1', 'name2'],
cohort_ids: [i, i * i],
})
);
Expand Down Expand Up @@ -151,6 +158,26 @@ MockedSuccess.parameters = {
);
}
),
rest.post(
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
(req, res, ctx) => {
return res(
ctx.delay(2000),
ctx.json({
bins: generateHistogramTestData(),
})
);
}
),
rest.post(
'http://:cohortmiddlewarepath/cohort-middleware/cohort-stats/check-overlap/by-source-id/:sourceid/by-cohort-definition-ids/:cohortdefinitionA/:cohortdefinitionB',
(req, res, ctx) => {
const { cohortmiddlewarepath } = req.params;
const { cohortdefinitionA } = req.params;
const { cohortdefinitionB } = req.params;
return res(ctx.delay(1100), ctx.json(generateEulerTestData()));
}
),
],
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useQuery } from 'react-query';
import { Spin } from 'antd';
import { Spin, Button } from 'antd';
import { fetchConceptStatsByHareSubset } from '../../../Utils/cohortMiddlewareApi';
import queryConfig from '../../../../SharedUtils/QueryConfig';
import BarChart from '../ChartIcons/BarChart';
Expand All @@ -14,6 +14,8 @@ const AttritionTableRow = ({
rowObject,
outcome,
currentCovariateAndCovariatesFromPrecedingRows,
modalInfo,
setModalInfo,
}) => {
const sourceId = useSourceContext().source;
const [breakdownSize, setBreakdownSize] = useState(null);
Expand Down Expand Up @@ -130,13 +132,32 @@ const AttritionTableRow = ({
return value;
};

const determineModalTitle = () => {
let title = rowObject.variable_type === 'concept' ? 'Continuous ' : 'Dichotomous ';
title += rowType;
title += rowType === 'Outcome' ? ' Phenotype' : '';
return title;
};
const handleChartIconClick = () => {
setModalInfo({
...modalInfo,
title: determineModalTitle(),
isModalOpen: true,
currentCovariateAndCovariatesFromPrecedingRows,
rowObject,
rowType,
});
};

return (
<tr>
<td className='gwasv2-attrition-table--leftpad'>
{rowType === 'Outcome' ? 'Outcome Phenotype' : rowType}
</td>
<td className='gwasv2-attrition-table--chart'>
{determineChartIcon(rowType)}
<Button aria-label='show attrition table modal' type='text' onClick={handleChartIconClick}>
{determineChartIcon(rowType)}
</Button>
</td>
<td>{rowName()}</td>
<td className='gwasv2-attrition-table--rightborder'>
Expand All @@ -158,11 +179,15 @@ AttritionTableRow.propTypes = {
outcome: PropTypes.object,
rowObject: PropTypes.object,
selectedCohort: PropTypes.object.isRequired,
modalInfo: PropTypes.object,
setModalInfo: PropTypes.func,
};

AttritionTableRow.defaultProps = {
outcome: null,
rowObject: null,
modalInfo: null,
setModalInfo: () => null,
};

export default AttritionTableRow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.attrition-table-modal .ant-modal-content {
width: 650px;
min-height: 480px;
}

.attrition-table-modal .ant-modal-body {
margin: 0 auto;
}

.attrition-table-modal h3 {
color: #2e77b8;
font-size: 14px;
}

.attrition-table-modal h4,
.attrition-table-modal .histrogram-loading,
.attrition-table-modal .euler-loading {
text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal } from 'antd';
import './AttritionTableModal.css';
import PhenotypeHistogram from '../../Diagrams/PhenotypeHistogram/PhenotypeHistogram';
import CohortsOverlapDiagram from '../../Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram';

const AttritionTableModal = ({ modalInfo, setModalInfo }) => {
const modalWidth = 650;
const rowIsOutcome = modalInfo.rowType === 'Outcome';

return (
<Modal
title={<h3>{modalInfo.title}</h3>}
open={modalInfo.isModalOpen}
onOk={() => setModalInfo({ ...modalInfo, isModalOpen: false })}
onCancel={() => setModalInfo({ ...modalInfo, isModalOpen: false })}
footer={null}
width={modalWidth}
className='attrition-table-modal'
>
{modalInfo?.rowObject
&& modalInfo.rowObject.variable_type === 'concept' && (
<div data-testid='phenotype-histogram-diagram'>
<PhenotypeHistogram
useInlineErrorMessages
selectedStudyPopulationCohort={modalInfo.selectedCohort}
selectedCovariates={
modalInfo.currentCovariateAndCovariatesFromPrecedingRows
}
outcome={modalInfo.outcome}
selectedContinuousItem={modalInfo.rowObject}
useAnimation={false}
/>
</div>
)}
{modalInfo?.rowObject
&& modalInfo.rowObject.variable_type === 'custom_dichotomous' && (
<div data-testid='euler-diagram'>
<CohortsOverlapDiagram
useInlineErrorMessages
selectedStudyPopulationCohort={modalInfo.selectedCohort}
selectedCaseCohort={{
cohort_name: modalInfo?.rowObject?.cohort_names[0],
size: modalInfo?.rowObject?.cohort_sizes[0],
cohort_definition_id: modalInfo?.rowObject?.cohort_ids[0],
}}
selectedControlCohort={{
cohort_name: modalInfo?.rowObject?.cohort_names[1],
size: modalInfo?.rowObject?.cohort_sizes[1],
cohort_definition_id: modalInfo?.rowObject?.cohort_ids[1],
}}
selectedCovariates={rowIsOutcome ? [] : modalInfo.currentCovariateAndCovariatesFromPrecedingRows}
outcome={rowIsOutcome ? null : modalInfo.outcome}
diagramId='modal-euler'
/>
</div>
)}
</Modal>
);
};

AttritionTableModal.propTypes = {
modalInfo: PropTypes.object.isRequired,
setModalInfo: PropTypes.func.isRequired,
};

export default AttritionTableModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { QueryClient, QueryClientProvider } from 'react-query';
import AttritionTableModal from './AttritionTableModal';
import { SourceContextProvider } from '../../../Utils/Source';
import {
fetchSimpleOverlapInfo,
useSourceFetch,
} from '../../../Utils/cohortMiddlewareApi';

// Mock the PhenotypeHistogram component
jest.mock(
'../../Diagrams/PhenotypeHistogram/PhenotypeHistogram',
() => () => null,
);
jest.mock('../../../Utils/cohortMiddlewareApi');
fetchSimpleOverlapInfo.mockResolvedValue({
cohort_overlap: {
case_control_overlap: 123,
},
});
useSourceFetch.mockResolvedValue({
sourceId: 2,
loading: false,
});
console.error = jest.fn();

describe('AttritionTableModal', () => {
const mockSetModalInfo = jest.fn();
const defaultProps = {
modalInfo: {
isModalOpen: true,
title: 'Test Modal',
rowObject: {
variable_type: 'concept',
},
selectedCohort: {},
currentCovariateAndCovariatesFromPrecedingRows: [],
outcome: {},
},
setModalInfo: mockSetModalInfo,
};
beforeEach(() => {
jest.clearAllMocks();
});

it('renders modal with title and histogram content', () => {
render(<AttritionTableModal {...defaultProps} />);
expect(screen.getByText('Test Modal')).toBeInTheDocument();
expect(
screen.queryByTestId('phenotype-histogram-diagram'),
).toBeInTheDocument();
expect(screen.queryByTestId('euler-diagram')).not.toBeInTheDocument();
});

it('displays Euler Diagram when variable_type is custom_dichotomous', () => {
const mockedQueryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
const customProps = {
...defaultProps,
modalInfo: {
...defaultProps.modalInfo,
rowObject: {
variable_type: 'custom_dichotomous',
cohort_names: [1],
cohort_ids: [2],
cohort_sizes: [3],
},
},
};
render(
<QueryClientProvider client={mockedQueryClient}>
<SourceContextProvider>
<AttritionTableModal {...customProps} />
</SourceContextProvider>
</QueryClientProvider>,
);
expect(screen.queryByTestId('euler-diagram')).toBeInTheDocument();
expect(
screen.queryByTestId('phenotype-histogram-diagram'),
).not.toBeInTheDocument();
});

it('does not render content if rowObject is not provided', () => {
const noRowObjectProps = {
...defaultProps,
modalInfo: {
...defaultProps.modalInfo,
rowObject: null,
},
};
render(<AttritionTableModal {...noRowObjectProps} />);
expect(screen.getByText('Test Modal')).toBeInTheDocument();
expect(
screen.queryByTestId('phenotype-histogram-diagram'),
).not.toBeInTheDocument();
expect(screen.queryByTestId('euler-diagram')).not.toBeInTheDocument();
});
});
Loading

0 comments on commit e60c8be

Please sign in to comment.