Skip to content

Commit

Permalink
Feat/vadc sprint20 (#1610)
Browse files Browse the repository at this point in the history
* Bugfix/vadc defect20 unlabeled3 button (#1603)

* PPS-1564 PPS-1567 PPS-1585: update guppy to 0.19.1 (#1601)

* bugfix(vadcDefect20Unlabled3Button): initial commit

* bugfix(vadcDefect20Unlabled3Button): reverted unintentionally changed files

---------

Co-authored-by: Shawn O'Connor <[email protected]>

* Feat/attrition table histogram modal (#1600)

* feat(attritionTableHistogramModel): Initial commit

* feat(attritionTableHistogramModal): Initial commit

* feat(attritionTableHistogramModal): added CSS, notes for technical approach, func to determine modal title

* feat(attritionTableHistogramModal): Ran linter, reverted unintentionally changed file

* feat(attritionTableHistogramModal): Data wrangled needed props for histogram into modal

* feat(attritionTableHistogramModal): Ran linter

* feat(attritionTableHistogramModal): Added logic to render histogram and display messages inline, added styles for modal

* feat(attritionTableHistogramModal): updated to use currentCovariateAndCovariatesFromPrecedingRows instead of covariates, ran linter

* feat(attritionaTableHistogramModal): updated storybook

* feat(attritionaTableHistogramModal): code cleanup - remove dev notes file, unneeded vars

* feat(attritionaTableHistogramModal): code cleanup - removed unneeded dispatch var

* feat(attritionaTableHistogramModal): code cleanup - changed order of propTypes object keys

* feat(attritionTableHistogramModal): Wrote unit test

* feat(attritionTableHistogramModal): ran linter

* feat(attritionTableHistogramModal): refactored unit test

* feat(attritionTableHistogramModal): added logic for euler diagram viz

* feat(attritionTableHistogramModal): added logic for euler diagram, updated unit tests

* feat(attritionTableHistogramModal): updated stories to show euler diagrams

* feat(attritionTableHistogramModal): updated stories to render euler diagrams that actually show diagram

* feat(attritionTableHistogramModal): code cleanup - removal of unneeded log statements and vars

* feat(attritionTableHistogramModal: ran eslint

* feat(attritionTableHistogramModal): Formatted code, added aria label to button

* feat(attritionTableHistogram): ran linter

* feat(attritionTableHistogram): ran linter

* feat(attritionTableHistogramModal): added prop and logic to disenable animation on histogram in modals

* feat(attritionTableHistogramModal): updated comments, ran linter

* feat(attritionTableHistogramModal): updated comment

* feat(attritionTableHistogramModal): removed unneeded file

* feat(attritionTableHistogramModal): added variable rowIsOutcome

* feat(attritionTableHistogramModal): ran linter and fixed spelling mistake in variable name

* feat(attritionTableHistogramModal): added default values for AttritionTableRow modalInfo obj and func, added code to try to force re-draws of viz

* feat(attritionTableHistogramModal): Ran linter, removed unneeded props from first row AttritionTableRow

* feat(attritionTableHistogramModal): Removed duplicate modal and hoisted existing modal to be a child of AttritionTableWrapper to try to resolve issue with outcome diagram not re-drawing

* feat(attritionTableHistogramModal): updated conditional for determining rowIsOutcome to use rowType string

* feat(attritionTableHistogramModal): ran linter

* feat(attritionTableHistogramModal): Updated ID ref used by Euler diagram in D3 call to be dynamic to avoid unexpected re-rendering

---------

Co-authored-by: Shawn O'Connor <[email protected]>
  • Loading branch information
jarvisraymond-uchicago and ocshawn authored Oct 23, 2024
1 parent 6b224f4 commit 7d03acf
Show file tree
Hide file tree
Showing 18 changed files with 465 additions and 74 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 7d03acf

Please sign in to comment.