Skip to content

Commit bed0245

Browse files
Feat/add transformation and filters to remaining frontend parts (#1672)
* fix: fix applyAutoGenFilters() to not return {} * fix: avoid null in variablesPayload * fix: add some -1/+1 padding to min/max histogram cutoffs * fix: correct histogram config in attrition table scenario * fix: fix order of the overlap request ...as the order matters now because of filtering and transformation...and outcome should go first * feat: make transformation and filter fields readonly when in modal * feat: improve documentation on fetchSimpleOverlapInfo ...and fix eslint * fix: fix order of variable in submitEndpoint call * fix: allow for negative values in min/max cutoffs for histograms ...and improve code readability * feat(add_transformation_and_filters_to_remaining_frontend_parts): Added CSS rules to make modal render closer to input view * feat(add_transformation_and_filters_to_remaining_frontend_parts): Moved 3rd column over a touch to better match other view --------- Co-authored-by: Jarvis Raymond <jarvisraymond@uchicago.edu>
1 parent b630445 commit bed0245

File tree

7 files changed

+95
-85
lines changed

7 files changed

+95
-85
lines changed

src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.jsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const AttritionTable = ({
4949
'Autogenerated variable for filtering out Case Population',
5050
};
5151
}
52-
return {};
52+
return null;
5353
};
5454

5555
return (
@@ -106,9 +106,9 @@ const AttritionTable = ({
106106
rowType='Outcome'
107107
outcome={outcome}
108108
rowObject={outcome}
109-
currentCovariateAndCovariatesFromPrecedingRows={[
110-
applyAutoGenFilters(),
111-
]}
109+
currentCovariateAndCovariatesFromPrecedingRows={
110+
(applyAutoGenFilters() ? [applyAutoGenFilters()] : [])
111+
}
112112
modalInfo={modalInfo}
113113
setModalInfo={setModalInfo}
114114
/>
@@ -129,7 +129,7 @@ const AttritionTable = ({
129129
rowType='Covariate'
130130
currentCovariateAndCovariatesFromPrecedingRows={[
131131
...item,
132-
applyAutoGenFilters(),
132+
...(applyAutoGenFilters() ? [applyAutoGenFilters()] : []),
133133
]}
134134
modalInfo={modalInfo}
135135
setModalInfo={setModalInfo}

src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.css

+26
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,29 @@
1717
.attrition-table-modal .euler-loading {
1818
text-align: center;
1919
}
20+
21+
.attrition-table-modal .GWASUI-row {
22+
display: flex;
23+
flex-direction: row;
24+
flex-wrap: wrap;
25+
width: 100%;
26+
}
27+
28+
.attrition-table-modal .GWASUI-column {
29+
display: flex;
30+
flex-direction: column;
31+
flex-basis: 100%;
32+
flex: 1;
33+
}
34+
35+
.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(odd) {
36+
min-width: 180px;
37+
}
38+
39+
.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(3) {
40+
margin-left: 35px;
41+
}
42+
43+
.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(even) {
44+
margin-left: -25px;
45+
}

src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.jsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,23 @@ const AttritionTableModal = ({ modalInfo, setModalInfo }) => {
2424
<div data-testid='phenotype-histogram-diagram'>
2525
<PhenotypeHistogram
2626
selectedStudyPopulationCohort={modalInfo.selectedCohort}
27-
selectedCovariates={
28-
modalInfo.currentCovariateAndCovariatesFromPrecedingRows
29-
}
30-
outcome={modalInfo.outcome}
27+
selectedCovariates={(() => {
28+
// If row is outcome, we don't want covariates to be included in the filter.
29+
// If not, we only want covariates from previous rows here. The current one will be in selectedContinuousItem below.
30+
// Overall, the result of the logic below should be a histogram that reflects what was displayed in the "select covariate"
31+
// step (i.e. not the data that remains *after* filtering, but what was displayed while selecting the covariate).
32+
if (rowIsOutcome) return [];
33+
34+
if (modalInfo.outcome.variable_type === 'custom_dichotomous') {
35+
// case/control... - here we also remove an extra item that is added on the fly (see applyAutoGenFilters() in AttritionTable)
36+
return modalInfo.currentCovariateAndCovariatesFromPrecedingRows.slice(0, -2);
37+
}
38+
39+
return modalInfo.currentCovariateAndCovariatesFromPrecedingRows.slice(0, -1);
40+
})()}
41+
outcome={rowIsOutcome ? null : modalInfo.outcome}
3142
selectedContinuousItem={modalInfo.rowObject}
32-
useAnimation={false}
43+
readOnly
3344
/>
3445
</div>
3546
)}

src/Analysis/GWASApp/Components/Diagrams/PhenotypeHistogram/PhenotypeHistogram.css

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
.GWASUI-column.transformation-dropdown-label {
2-
max-width: 25%;
2+
max-width: 30%;
3+
padding-top: 10px;
4+
padding-bottom: 5px;
35
}
46

57
.GWASUI-column.transformation-select {
6-
max-width: 65%;
8+
max-width: 60%;
9+
padding-bottom: 10px;
710
}
811

912
label[for='input-minOutlierCutoff'],

src/Analysis/GWASApp/Components/Diagrams/PhenotypeHistogram/PhenotypeHistogram.jsx

+39-22
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const PhenotypeHistogram = ({
1717
selectedCovariates,
1818
outcome,
1919
selectedContinuousItem,
20-
useAnimation,
20+
readOnly,
2121
handleChangeTransformation,
2222
handleChangeMinOutlierCutoff,
2323
handleChangeMaxOutlierCutoff,
@@ -49,6 +49,14 @@ const PhenotypeHistogram = ({
4949
queryConfig,
5050
);
5151

52+
const getMinCutoff = (continuousItem) => continuousItem?.filters?.find(
53+
(filter) => filter.type === FILTERS.greaterThanOrEqualTo,
54+
)?.value ?? null;
55+
56+
const getMaxCutoff = (continuousItem) => continuousItem?.filters?.find(
57+
(filter) => filter.type === FILTERS.lessThanOrEqualTo,
58+
)?.value ?? null;
59+
5260
useEffect(() => {
5361
// Validate and give error message if there is no data:
5462
if (
@@ -70,6 +78,9 @@ const PhenotypeHistogram = ({
7078
payload: MESSAGES.NO_BINS_ERROR,
7179
});
7280
}
81+
setMinOutlierCutoff(getMinCutoff(selectedContinuousItem));
82+
setMaxOutlierCutoff(getMaxCutoff(selectedContinuousItem));
83+
setSelectedTransformation(selectedContinuousItem.transformation);
7384
}
7485
}, [data]);
7586

@@ -90,33 +101,31 @@ const PhenotypeHistogram = ({
90101
barColor: 'darkblue',
91102
xAxisLegend: selectedContinuousItem.concept_name,
92103
yAxisLegend: 'Persons',
93-
useAnimation,
94-
minCutoff:
95-
selectedContinuousItem.filters?.find(
96-
(filter) => filter.type === FILTERS.greaterThanOrEqualTo,
97-
)?.value ?? undefined,
98-
maxCutoff:
99-
selectedContinuousItem.filters?.find(
100-
(filter) => filter.type === FILTERS.lessThanOrEqualTo,
101-
)?.value ?? undefined,
104+
useAnimation: !readOnly,
105+
minCutoff: getMinCutoff(selectedContinuousItem),
106+
maxCutoff: getMaxCutoff(selectedContinuousItem),
102107
};
103108
return (
104109
<React.Fragment>
105110
{inlineErrorMessage}
106111
{data.bins !== null && (
107112
<div>
113+
<div>{(outcome?.variable_type === 'custom_dichotomous' && readOnly
114+
? '\u2139\uFE0F histogram displaying data from both case and control groups'
115+
: '')}
116+
</div>
108117
<div className='GWASUI-row'>
109118
<div className='GWASUI-column transformation-dropdown-label'>
110119
<label
111120
id='transformation-dropdown-label'
112121
htmlFor='transformation-select'
113-
>
114-
Select Transformation
122+
>{(readOnly ? 'Selected Transformation' : 'Select Transformation')}
115123
</label>
116124
</div>
117125
<div className='GWASUI-column transformation-select'>
118126
<Select
119127
id='transformation-select'
128+
disabled={readOnly}
120129
showSearch={false}
121130
labelInValue
122131
value={selectedTransformation}
@@ -145,17 +154,18 @@ const PhenotypeHistogram = ({
145154
<div className='GWASUI-column'>
146155
<InputNumber
147156
id='input-minOutlierCutoff'
157+
disabled={readOnly}
148158
value={minOutlierCutoff}
149159
onChange={(value) => {
150160
setMinOutlierCutoff(value);
151161
handleChangeMinOutlierCutoff(value);
152162
}}
153-
min={data.bins[0]?.start || 0}
154-
max={
155-
maxOutlierCutoff
156-
|| data.bins[data.bins.length - 1]?.end
157-
|| 100
158-
}
163+
min={(data.bins[0]?.start ?? 0) - 1}
164+
max={(() => {
165+
const lastBinEnd = data.bins[data.bins.length - 1]?.end;
166+
const cutOffValue = maxOutlierCutoff ?? lastBinEnd ?? 100;
167+
return cutOffValue + 1;
168+
})()}
159169
onKeyDown={(e) => {
160170
const { key } = e;
161171
// Allow only numeric keys, backspace, and delete, and one decimal point
@@ -165,6 +175,7 @@ const PhenotypeHistogram = ({
165175
&& key !== 'Delete'
166176
&& key !== 'ArrowLeft'
167177
&& key !== 'ArrowRight'
178+
&& (key !== '-' || e.target.value.includes('-'))
168179
&& (key !== '.' || e.target.value.includes('.'))
169180
) {
170181
e.preventDefault();
@@ -180,13 +191,18 @@ const PhenotypeHistogram = ({
180191
<div className='GWASUI-column'>
181192
<InputNumber
182193
id='input-maxOutlierCutoff'
194+
disabled={readOnly}
183195
value={maxOutlierCutoff}
184196
onChange={(value) => {
185197
setMaxOutlierCutoff(value);
186198
handleChangeMaxOutlierCutoff(value);
187199
}}
188-
min={minOutlierCutoff || data.bins[0]?.start || 0}
189-
max={data.bins[data.bins.length - 1]?.end || 100}
200+
min={(() => {
201+
const firstBinStart = data.bins[0]?.start;
202+
const cutOffValue = minOutlierCutoff ?? firstBinStart ?? 0;
203+
return cutOffValue - 1;
204+
})()}
205+
max={(data.bins[data.bins.length - 1]?.end ?? 100) + 1}
190206
onKeyDown={(e) => {
191207
const { key } = e;
192208
// Allow only numeric keys, backspace, and delete, and one decimal point
@@ -196,6 +212,7 @@ const PhenotypeHistogram = ({
196212
&& key !== 'Delete'
197213
&& key !== 'ArrowLeft'
198214
&& key !== 'ArrowRight'
215+
&& (key !== '-' || e.target.value.includes('-'))
199216
&& (key !== '.' || e.target.value.includes('.'))
200217
) {
201218
e.preventDefault();
@@ -216,7 +233,7 @@ PhenotypeHistogram.propTypes = {
216233
selectedCovariates: PropTypes.array,
217234
outcome: PropTypes.object,
218235
selectedContinuousItem: PropTypes.object.isRequired,
219-
useAnimation: PropTypes.bool,
236+
readOnly: PropTypes.bool,
220237
handleChangeTransformation: PropTypes.func,
221238
handleChangeMinOutlierCutoff: PropTypes.func,
222239
handleChangeMaxOutlierCutoff: PropTypes.func,
@@ -226,7 +243,7 @@ PhenotypeHistogram.defaultProps = {
226243
dispatch: null,
227244
selectedCovariates: [],
228245
outcome: null,
229-
useAnimation: true,
246+
readOnly: false,
230247
handleChangeTransformation: null,
231248
handleChangeMinOutlierCutoff: null,
232249
handleChangeMaxOutlierCutoff: null,

src/Analysis/GWASApp/Utils/cohortMiddlewareApi.js

+3-50
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const fetchSimpleOverlapInfo = async (
1313
outcome,
1414
) => {
1515
const variablesPayload = {
16-
variables: [...selectedCovariates, outcome,
16+
variables: [outcome, ...selectedCovariates, // <- note: this order is important (outcome first, then covariates)
1717
// add extra filter to make sure we only count persons that have a HARE group as well:
1818
{
1919
variable_type: 'concept',
@@ -45,7 +45,7 @@ export const fetchHistogramInfo = async (
4545
transformationType,
4646
) => {
4747
const variablesPayload = {
48-
variables: [...selectedCovariates, outcome,
48+
variables: [outcome, ...selectedCovariates, // <- note: this order is important (outcome first, then covariates)
4949
// add extra filter to make sure we only count persons that have a HARE group as well:
5050
{
5151
variable_type: 'concept',
@@ -81,7 +81,7 @@ export const fetchConceptStatsByHareSubset = async (
8181
sourceId,
8282
) => {
8383
const variablesPayload = {
84-
variables: [outcome, ...subsetCovariates],
84+
variables: [...(outcome !== null ? [outcome] : []), ...subsetCovariates],
8585
};
8686
const conceptStatsEndPoint = `${cohortMiddlewarePath}concept-stats/by-source-id/${sourceId}/by-cohort-definition-id/${cohortDefinitionId}/breakdown-by-concept-id/${hareConceptId}`;
8787
const reqBody = {
@@ -113,53 +113,6 @@ export const addCDFilter = (cohortId, otherCohortId, covariateArr) => {
113113
return covariateRequest;
114114
};
115115

116-
export const fetchConceptStatsByHareSubsetCC = async (
117-
cohortDefinitionId,
118-
otherCohortDefinitionId,
119-
covariateSubset,
120-
sourceId,
121-
) => fetchConceptStatsByHareSubset(
122-
cohortDefinitionId,
123-
addCDFilter(cohortDefinitionId, otherCohortDefinitionId, covariateSubset),
124-
sourceId,
125-
);
126-
127-
export const fetchConceptStatsByHareForCaseControl = async (
128-
queriedCohortDefinitionId,
129-
otherCohortDefinitionId,
130-
selectedCovariates,
131-
selectedDichotomousCovariates,
132-
sourceId,
133-
) => fetchConceptStatsByHareSubset(
134-
queriedCohortDefinitionId,
135-
addCDFilter(queriedCohortDefinitionId, otherCohortDefinitionId, [
136-
...selectedCovariates,
137-
...selectedDichotomousCovariates,
138-
]),
139-
sourceId,
140-
);
141-
142-
export const fetchCovariateStats = async (
143-
cohortDefinitionId,
144-
selectedCovariateIds,
145-
sourceId,
146-
) => {
147-
const covariateIds = { ConceptIds: selectedCovariateIds };
148-
const conceptStatsEndpoint = `${cohortMiddlewarePath}concept-stats/by-source-id/${sourceId}/by-cohort-definition-id/${cohortDefinitionId}`;
149-
const reqBody = {
150-
method: 'POST',
151-
credentials: 'include',
152-
headers,
153-
body: JSON.stringify(covariateIds),
154-
};
155-
const response = await fetch(conceptStatsEndpoint, reqBody);
156-
if (!response.ok) {
157-
const message = `An error has occured: ${response.status}`;
158-
throw new Error(message);
159-
}
160-
return response.json();
161-
};
162-
163116
export const fetchCohortDefinitions = async (sourceId, selectedTeamProject) => {
164117
const cohortEndPoint = `${cohortMiddlewarePath}cohortdefinition-stats/by-source-id/${sourceId}/by-team-project?team-project=${selectedTeamProject}`;
165118
const response = await fetch(cohortEndPoint);

src/Analysis/GWASApp/Utils/gwasWorkflowApi.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const jobSubmission = async (
1616
const submitEndpoint = `${gwasWorkflowPath}submit`;
1717
const requestBody = {
1818
n_pcs: numOfPCs,
19-
variables: [...selectedCovariates, outcome],
19+
variables: [outcome, ...selectedCovariates], // <- note: this order is important (outcome first, then covariates)
2020
out_prefix: Date.now().toString(),
2121
outcome,
2222
hare_population: selectedHare.concept_value_name,

0 commit comments

Comments
 (0)