Skip to content

Commit

Permalink
Feat/add transformation and filters to remaining frontend parts (#1672)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
pieterlukasse and jarvisraymond-uchicago authored Feb 20, 2025
1 parent b630445 commit bed0245
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const AttritionTable = ({
'Autogenerated variable for filtering out Case Population',
};
}
return {};
return null;
};

return (
Expand Down Expand Up @@ -106,9 +106,9 @@ const AttritionTable = ({
rowType='Outcome'
outcome={outcome}
rowObject={outcome}
currentCovariateAndCovariatesFromPrecedingRows={[
applyAutoGenFilters(),
]}
currentCovariateAndCovariatesFromPrecedingRows={
(applyAutoGenFilters() ? [applyAutoGenFilters()] : [])
}
modalInfo={modalInfo}
setModalInfo={setModalInfo}
/>
Expand All @@ -129,7 +129,7 @@ const AttritionTable = ({
rowType='Covariate'
currentCovariateAndCovariatesFromPrecedingRows={[
...item,
applyAutoGenFilters(),
...(applyAutoGenFilters() ? [applyAutoGenFilters()] : []),
]}
modalInfo={modalInfo}
setModalInfo={setModalInfo}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,29 @@
.attrition-table-modal .euler-loading {
text-align: center;
}

.attrition-table-modal .GWASUI-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}

.attrition-table-modal .GWASUI-column {
display: flex;
flex-direction: column;
flex-basis: 100%;
flex: 1;
}

.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(odd) {
min-width: 180px;
}

.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(3) {
margin-left: 35px;
}

.attrition-table-modal .outlier-inputs .GWASUI-column:nth-child(even) {
margin-left: -25px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ const AttritionTableModal = ({ modalInfo, setModalInfo }) => {
<div data-testid='phenotype-histogram-diagram'>
<PhenotypeHistogram
selectedStudyPopulationCohort={modalInfo.selectedCohort}
selectedCovariates={
modalInfo.currentCovariateAndCovariatesFromPrecedingRows
}
outcome={modalInfo.outcome}
selectedCovariates={(() => {
// If row is outcome, we don't want covariates to be included in the filter.
// If not, we only want covariates from previous rows here. The current one will be in selectedContinuousItem below.
// Overall, the result of the logic below should be a histogram that reflects what was displayed in the "select covariate"
// step (i.e. not the data that remains *after* filtering, but what was displayed while selecting the covariate).
if (rowIsOutcome) return [];

if (modalInfo.outcome.variable_type === 'custom_dichotomous') {
// case/control... - here we also remove an extra item that is added on the fly (see applyAutoGenFilters() in AttritionTable)
return modalInfo.currentCovariateAndCovariatesFromPrecedingRows.slice(0, -2);
}

return modalInfo.currentCovariateAndCovariatesFromPrecedingRows.slice(0, -1);
})()}
outcome={rowIsOutcome ? null : modalInfo.outcome}
selectedContinuousItem={modalInfo.rowObject}
useAnimation={false}
readOnly
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
.GWASUI-column.transformation-dropdown-label {
max-width: 25%;
max-width: 30%;
padding-top: 10px;
padding-bottom: 5px;
}

.GWASUI-column.transformation-select {
max-width: 65%;
max-width: 60%;
padding-bottom: 10px;
}

label[for='input-minOutlierCutoff'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PhenotypeHistogram = ({
selectedCovariates,
outcome,
selectedContinuousItem,
useAnimation,
readOnly,
handleChangeTransformation,
handleChangeMinOutlierCutoff,
handleChangeMaxOutlierCutoff,
Expand Down Expand Up @@ -49,6 +49,14 @@ const PhenotypeHistogram = ({
queryConfig,
);

const getMinCutoff = (continuousItem) => continuousItem?.filters?.find(
(filter) => filter.type === FILTERS.greaterThanOrEqualTo,
)?.value ?? null;

const getMaxCutoff = (continuousItem) => continuousItem?.filters?.find(
(filter) => filter.type === FILTERS.lessThanOrEqualTo,
)?.value ?? null;

useEffect(() => {
// Validate and give error message if there is no data:
if (
Expand All @@ -70,6 +78,9 @@ const PhenotypeHistogram = ({
payload: MESSAGES.NO_BINS_ERROR,
});
}
setMinOutlierCutoff(getMinCutoff(selectedContinuousItem));
setMaxOutlierCutoff(getMaxCutoff(selectedContinuousItem));
setSelectedTransformation(selectedContinuousItem.transformation);
}
}, [data]);

Expand All @@ -90,33 +101,31 @@ const PhenotypeHistogram = ({
barColor: 'darkblue',
xAxisLegend: selectedContinuousItem.concept_name,
yAxisLegend: 'Persons',
useAnimation,
minCutoff:
selectedContinuousItem.filters?.find(
(filter) => filter.type === FILTERS.greaterThanOrEqualTo,
)?.value ?? undefined,
maxCutoff:
selectedContinuousItem.filters?.find(
(filter) => filter.type === FILTERS.lessThanOrEqualTo,
)?.value ?? undefined,
useAnimation: !readOnly,
minCutoff: getMinCutoff(selectedContinuousItem),
maxCutoff: getMaxCutoff(selectedContinuousItem),
};
return (
<React.Fragment>
{inlineErrorMessage}
{data.bins !== null && (
<div>
<div>{(outcome?.variable_type === 'custom_dichotomous' && readOnly
? '\u2139\uFE0F histogram displaying data from both case and control groups'
: '')}
</div>
<div className='GWASUI-row'>
<div className='GWASUI-column transformation-dropdown-label'>
<label
id='transformation-dropdown-label'
htmlFor='transformation-select'
>
Select Transformation
>{(readOnly ? 'Selected Transformation' : 'Select Transformation')}
</label>
</div>
<div className='GWASUI-column transformation-select'>
<Select
id='transformation-select'
disabled={readOnly}
showSearch={false}
labelInValue
value={selectedTransformation}
Expand Down Expand Up @@ -145,17 +154,18 @@ const PhenotypeHistogram = ({
<div className='GWASUI-column'>
<InputNumber
id='input-minOutlierCutoff'
disabled={readOnly}
value={minOutlierCutoff}
onChange={(value) => {
setMinOutlierCutoff(value);
handleChangeMinOutlierCutoff(value);
}}
min={data.bins[0]?.start || 0}
max={
maxOutlierCutoff
|| data.bins[data.bins.length - 1]?.end
|| 100
}
min={(data.bins[0]?.start ?? 0) - 1}
max={(() => {
const lastBinEnd = data.bins[data.bins.length - 1]?.end;
const cutOffValue = maxOutlierCutoff ?? lastBinEnd ?? 100;
return cutOffValue + 1;
})()}
onKeyDown={(e) => {
const { key } = e;
// Allow only numeric keys, backspace, and delete, and one decimal point
Expand All @@ -165,6 +175,7 @@ const PhenotypeHistogram = ({
&& key !== 'Delete'
&& key !== 'ArrowLeft'
&& key !== 'ArrowRight'
&& (key !== '-' || e.target.value.includes('-'))
&& (key !== '.' || e.target.value.includes('.'))
) {
e.preventDefault();
Expand All @@ -180,13 +191,18 @@ const PhenotypeHistogram = ({
<div className='GWASUI-column'>
<InputNumber
id='input-maxOutlierCutoff'
disabled={readOnly}
value={maxOutlierCutoff}
onChange={(value) => {
setMaxOutlierCutoff(value);
handleChangeMaxOutlierCutoff(value);
}}
min={minOutlierCutoff || data.bins[0]?.start || 0}
max={data.bins[data.bins.length - 1]?.end || 100}
min={(() => {
const firstBinStart = data.bins[0]?.start;
const cutOffValue = minOutlierCutoff ?? firstBinStart ?? 0;
return cutOffValue - 1;
})()}
max={(data.bins[data.bins.length - 1]?.end ?? 100) + 1}
onKeyDown={(e) => {
const { key } = e;
// Allow only numeric keys, backspace, and delete, and one decimal point
Expand All @@ -196,6 +212,7 @@ const PhenotypeHistogram = ({
&& key !== 'Delete'
&& key !== 'ArrowLeft'
&& key !== 'ArrowRight'
&& (key !== '-' || e.target.value.includes('-'))
&& (key !== '.' || e.target.value.includes('.'))
) {
e.preventDefault();
Expand All @@ -216,7 +233,7 @@ PhenotypeHistogram.propTypes = {
selectedCovariates: PropTypes.array,
outcome: PropTypes.object,
selectedContinuousItem: PropTypes.object.isRequired,
useAnimation: PropTypes.bool,
readOnly: PropTypes.bool,
handleChangeTransformation: PropTypes.func,
handleChangeMinOutlierCutoff: PropTypes.func,
handleChangeMaxOutlierCutoff: PropTypes.func,
Expand All @@ -226,7 +243,7 @@ PhenotypeHistogram.defaultProps = {
dispatch: null,
selectedCovariates: [],
outcome: null,
useAnimation: true,
readOnly: false,
handleChangeTransformation: null,
handleChangeMinOutlierCutoff: null,
handleChangeMaxOutlierCutoff: null,
Expand Down
53 changes: 3 additions & 50 deletions src/Analysis/GWASApp/Utils/cohortMiddlewareApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const fetchSimpleOverlapInfo = async (
outcome,
) => {
const variablesPayload = {
variables: [...selectedCovariates, outcome,
variables: [outcome, ...selectedCovariates, // <- note: this order is important (outcome first, then covariates)
// add extra filter to make sure we only count persons that have a HARE group as well:
{
variable_type: 'concept',
Expand Down Expand Up @@ -45,7 +45,7 @@ export const fetchHistogramInfo = async (
transformationType,
) => {
const variablesPayload = {
variables: [...selectedCovariates, outcome,
variables: [outcome, ...selectedCovariates, // <- note: this order is important (outcome first, then covariates)
// add extra filter to make sure we only count persons that have a HARE group as well:
{
variable_type: 'concept',
Expand Down Expand Up @@ -81,7 +81,7 @@ export const fetchConceptStatsByHareSubset = async (
sourceId,
) => {
const variablesPayload = {
variables: [outcome, ...subsetCovariates],
variables: [...(outcome !== null ? [outcome] : []), ...subsetCovariates],
};
const conceptStatsEndPoint = `${cohortMiddlewarePath}concept-stats/by-source-id/${sourceId}/by-cohort-definition-id/${cohortDefinitionId}/breakdown-by-concept-id/${hareConceptId}`;
const reqBody = {
Expand Down Expand Up @@ -113,53 +113,6 @@ export const addCDFilter = (cohortId, otherCohortId, covariateArr) => {
return covariateRequest;
};

export const fetchConceptStatsByHareSubsetCC = async (
cohortDefinitionId,
otherCohortDefinitionId,
covariateSubset,
sourceId,
) => fetchConceptStatsByHareSubset(
cohortDefinitionId,
addCDFilter(cohortDefinitionId, otherCohortDefinitionId, covariateSubset),
sourceId,
);

export const fetchConceptStatsByHareForCaseControl = async (
queriedCohortDefinitionId,
otherCohortDefinitionId,
selectedCovariates,
selectedDichotomousCovariates,
sourceId,
) => fetchConceptStatsByHareSubset(
queriedCohortDefinitionId,
addCDFilter(queriedCohortDefinitionId, otherCohortDefinitionId, [
...selectedCovariates,
...selectedDichotomousCovariates,
]),
sourceId,
);

export const fetchCovariateStats = async (
cohortDefinitionId,
selectedCovariateIds,
sourceId,
) => {
const covariateIds = { ConceptIds: selectedCovariateIds };
const conceptStatsEndpoint = `${cohortMiddlewarePath}concept-stats/by-source-id/${sourceId}/by-cohort-definition-id/${cohortDefinitionId}`;
const reqBody = {
method: 'POST',
credentials: 'include',
headers,
body: JSON.stringify(covariateIds),
};
const response = await fetch(conceptStatsEndpoint, reqBody);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
return response.json();
};

export const fetchCohortDefinitions = async (sourceId, selectedTeamProject) => {
const cohortEndPoint = `${cohortMiddlewarePath}cohortdefinition-stats/by-source-id/${sourceId}/by-team-project?team-project=${selectedTeamProject}`;
const response = await fetch(cohortEndPoint);
Expand Down
2 changes: 1 addition & 1 deletion src/Analysis/GWASApp/Utils/gwasWorkflowApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const jobSubmission = async (
const submitEndpoint = `${gwasWorkflowPath}submit`;
const requestBody = {
n_pcs: numOfPCs,
variables: [...selectedCovariates, outcome],
variables: [outcome, ...selectedCovariates], // <- note: this order is important (outcome first, then covariates)
out_prefix: Date.now().toString(),
outcome,
hare_population: selectedHare.concept_value_name,
Expand Down

0 comments on commit bed0245

Please sign in to comment.