Skip to content

Commit

Permalink
feat: fixed issues with incorrect specification for multi group estim… (
Browse files Browse the repository at this point in the history
#636)

* feat: fixed issues with incorrect specification for multi group estimators, added unit tests

* chore: remove unused import
  • Loading branch information
nicoalee authored Nov 30, 2023
1 parent 5e6c857 commit 69fe14a
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { IAnalysesSelection } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationDialogBase.types';
import { getWeightAndConditionsForSpecification } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationReview/CreateMetaAnalysisSpecificationReview.helpers';
import { EPropertyType } from 'components/EditMetadata';
import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete';

describe('CreateMetaAnalysisSpecificationReviewHelpers', () => {
describe('getWeightAndConditionsForSpecification', () => {
it('should set multiple weights and multiple conditions if the reference database is not default', () => {
const mockEstimator: IAutocompleteObject = {
label: 'ALESubtraction',
description: '',
};

const mockSelection: IAnalysesSelection = {
selectionKey: 'key',
selectionValue: 'val-selected',
referenceDataset: 'val-reference',
type: EPropertyType.STRING,
};

const result = getWeightAndConditionsForSpecification(mockEstimator, mockSelection);

expect(result.weights).toEqual([1, -1]);
expect(result.conditions).toEqual(['val-selected', 'val-reference']);
expect(result.databaseStudyset).toBeUndefined();
});

it('should return empty lists if the estimator is not defined', () => {
const mockSelection: IAnalysesSelection = {
selectionKey: 'key',
selectionValue: 'val',
referenceDataset: 'neuroquery',
type: EPropertyType.STRING,
};

const result = getWeightAndConditionsForSpecification(undefined, mockSelection);

expect(result.conditions.length).toEqual(0);
expect(result.weights.length).toEqual(0);
expect(result.databaseStudyset).toBeUndefined();
});

it('should set a single weight and a single condition if the reference database is default', () => {
const mockEstimator: IAutocompleteObject = {
label: 'ALESubtraction',
description: '',
};

const mockSelection: IAnalysesSelection = {
selectionKey: 'key',
selectionValue: 'val',
referenceDataset: 'neuroquery',
type: EPropertyType.STRING,
};

const result = getWeightAndConditionsForSpecification(mockEstimator, mockSelection);

expect(result.conditions.length).toEqual(1);
expect(result.weights).toEqual([1]);
expect(result.databaseStudyset).toEqual('neuroquery');
});

it('should parse the array into correct boolean types', () => {
let mockEstimator: IAutocompleteObject = {
label: 'ALESubtraction',
description: '',
};

let mockSelection: IAnalysesSelection = {
selectionKey: 'key',
selectionValue: 'true',
referenceDataset: 'false',
type: EPropertyType.BOOLEAN,
};

let result = getWeightAndConditionsForSpecification(mockEstimator, mockSelection);
expect(result.conditions).toEqual([true, false]);

mockEstimator = {
label: 'ALESubtraction',
description: '',
};

mockSelection = {
selectionKey: 'key',
selectionValue: true,
referenceDataset: 'false',
type: EPropertyType.BOOLEAN,
};

result = getWeightAndConditionsForSpecification(mockEstimator, mockSelection);
expect(result.conditions).toEqual([true, false]);
});

it('should parse the array into correct string types', () => {
let mockEstimator: IAutocompleteObject = {
label: 'ALESubtraction',
description: '',
};

let mockSelection: IAnalysesSelection = {
selectionKey: 'key',
selectionValue: 'true',
referenceDataset: 'false',
type: EPropertyType.STRING,
};

let result = getWeightAndConditionsForSpecification(mockEstimator, mockSelection);
expect(result.conditions).toEqual(['true', 'false']);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
selectedReferenceDatasetIsDefaultDataset,
} from '../CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import { IAnalysesSelection } from '../CreateMetaAnalysisSpecificationDialogBase.types';
import { EPropertyType } from 'components/EditMetadata';

export const getWeightAndConditionsForSpecification = (
estimator: IAutocompleteObject | null | undefined,
Expand Down Expand Up @@ -41,6 +42,21 @@ export const getWeightAndConditionsForSpecification = (
conditions = [selection.selectionValue] as string[] | boolean[];
}

// parse condition into correct type
conditions.forEach((condition, index) => {
switch (selection.type) {
case EPropertyType.BOOLEAN:
conditions[index] =
typeof condition === 'boolean' ? condition : condition === 'true';
break;
case EPropertyType.STRING:
conditions[index] = condition.toString();
break;
default:
throw new Error('unsupported selection type');
}
});

return {
weights,
conditions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const CreateMetaAnalysisSpecificationSelectionStepMultiGroup: React.FC<{
selectedValue: IAnalysesSelection;
}> = (props) => {
const { algorithm, onSelectValue, annotationId, selectedValue } = props;

const columnOptions = useInclusionColumnOptions(annotationId, selectedValue?.selectionKey);
const colOptionsToMultiGroupOptions: IMultiGroupOption[] = useMemo(() => {
return columnOptions
Expand All @@ -40,9 +39,7 @@ const CreateMetaAnalysisSpecificationSelectionStepMultiGroup: React.FC<{
const selectedOption = useMemo(() => {
if (!selectedValue.referenceDataset) return undefined;

const foundOption = multiGroupOptions.find(
(x) => x.label === selectedValue.referenceDataset
);
const foundOption = multiGroupOptions.find((x) => x.id === selectedValue.referenceDataset);
return foundOption;
}, [multiGroupOptions, selectedValue.referenceDataset]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
isMultiGroupAlgorithm,
selectedReferenceDatasetIsDefaultDataset,
} from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import {
DEFAULT_REFERENCE_DATASETS,
MULTIGROUP_ALGORITHMS,
} from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.types';

describe('SelectAnalysesComponentHelpers', () => {
describe('selectedReferenceDatasetIsDefaultDataset', () => {
it('should be truthy for default datasets', () => {
DEFAULT_REFERENCE_DATASETS.forEach((dataset) => {
const result = selectedReferenceDatasetIsDefaultDataset(dataset.id);
expect(result).toBeTruthy();
});
});

it('should return false for non reference datasets', () => {
const result = selectedReferenceDatasetIsDefaultDataset('random dataset');
expect(result).toBeFalsy();
});

it('should return false for undefined', () => {
const result = selectedReferenceDatasetIsDefaultDataset(undefined);
expect(result).toBeFalsy();
});
});

describe('isMultiGroupAlgorithm', () => {
it('should be truthy for multigroup algorithms', () => {
MULTIGROUP_ALGORITHMS.forEach((multigroupAlgorithm) => {
const result = isMultiGroupAlgorithm({
label: multigroupAlgorithm,
description: '',
});
expect(result).toBeTruthy();
});
});

it('should return false for non reference datasets', () => {
const result = isMultiGroupAlgorithm({ label: 'random', description: '' });
expect(result).toBeFalsy();
});

it('should return false for undefined', () => {
const result = isMultiGroupAlgorithm(undefined);
expect(result).toBeFalsy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,5 @@ export const selectedReferenceDatasetIsDefaultDataset = (
) => {
if (!selectedReferenceDataset) return false;

return DEFAULT_REFERENCE_DATASETS.some((x) => x.label === selectedReferenceDataset);
return DEFAULT_REFERENCE_DATASETS.some((x) => x.id === selectedReferenceDataset);
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ const SelectAnalysesSummaryComponent: React.FC<{

return (
<Box sx={{ display: 'flex' }}>
<Typography sx={{ marginRight: '0.5rem' }} variant="caption">
Included:
</Typography>{' '}
<Typography sx={{ marginRight: '0.5rem' }} variant="caption">
{count.studies} studies
</Typography>{' '}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import {
} from 'neurosynth-compose-typescript-sdk';
import { useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import BaseDialog, { IDialog } from '../BaseDialog';
import SelectSpecificationComponent from '../CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationAlgorithmStep/SelectSpecificationComponent/SelectSpecificationComponent';
import { IAnalysesSelection } from '../CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationDialogBase.types';
import SelectAnalysesComponent from '../CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent';
import { isMultiGroupAlgorithm } from '../CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import { getWeightAndConditionsForSpecification } from '../CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationReview/CreateMetaAnalysisSpecificationReview.helpers';
import BaseDialog, { IDialog } from 'components/Dialogs/BaseDialog';
import SelectSpecificationComponent from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationAlgorithmStep/SelectSpecificationComponent/SelectSpecificationComponent';
import { IAnalysesSelection } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationDialogBase.types';
import SelectAnalysesComponent from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent';
import { isMultiGroupAlgorithm } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import { getWeightAndConditionsForSpecification } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationReview/CreateMetaAnalysisSpecificationReview.helpers';
import CreateMetaAnalysisSpecificationSelectionStepMultiGroup from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/CreateMetaAnalysisSpecificationSelectionStepMultiGroup';

const metaAnalysisSpecification: IMetaAnalysisParamsSpecification = metaAnalysisSpec;

Expand Down Expand Up @@ -64,6 +65,10 @@ const EditSpecificationDialog: React.FC<IDialog> = (props) => {
selectionKey: specification.filter,
type: getType(specification?.conditions?.[0]),
selectionValue: (specification.conditions || [])[0],
referenceDataset:
specification?.conditions?.[1] !== undefined
? specification.conditions[1].toString()
: specification?.database_studyset || undefined,
});

const estimator = specification?.estimator?.type
Expand Down Expand Up @@ -103,7 +108,6 @@ const EditSpecificationDialog: React.FC<IDialog> = (props) => {
algorithmSpec.estimator,
selectedValue
);

mutate(
{
specificationId: specification.id,
Expand Down Expand Up @@ -133,6 +137,8 @@ const EditSpecificationDialog: React.FC<IDialog> = (props) => {
);
};

const isMultiGroup = isMultiGroupAlgorithm(algorithmSpec.estimator);

const disabled = useMemo(() => {
const isMultiGroup = isMultiGroupAlgorithm(algorithmSpec.estimator);
return (
Expand Down Expand Up @@ -191,6 +197,16 @@ const EditSpecificationDialog: React.FC<IDialog> = (props) => {
}}
algorithm={algorithmSpec}
/>
{isMultiGroup && (
<CreateMetaAnalysisSpecificationSelectionStepMultiGroup
onSelectValue={(newVal) => setSelectedValue(newVal)}
annotationId={
(metaAnalysis?.annotation as AnnotationReturn)?.neurostore_id || ''
}
selectedValue={selectedValue}
algorithm={algorithmSpec}
/>
)}
</Box>

<Box
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AxiosError, AxiosResponse } from 'axios';
import { useSnackbar } from 'notistack';
import { useMutation, useQueryClient } from 'react-query';
import API from 'utils/api';

const useDeleteMetaAnalysis = () => {
const queryClient = useQueryClient();
const { enqueueSnackbar } = useSnackbar();
return useMutation<AxiosResponse, AxiosError, string, unknown>(
(id) => API.NeurosynthServices.NeurosynthDefaultApi.metaAnalysesIdDelete(id),
{
onSuccess: () => {
queryClient.invalidateQueries('meta-analyses');
},
onError: () => {
enqueueSnackbar('There was an error deleting the meta-analysis', {
variant: 'error',
});
},
}
);
};

export default useDeleteMetaAnalysis;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useAuth0 } from '@auth0/auth0-react';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { Box, Button, Link, Paper, Typography } from '@mui/material';
import CodeSnippet from 'components/CodeSnippet/CodeSnippet';
import { isMultiGroupAlgorithm } from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import SelectAnalysesSummaryComponent from 'components/Dialogs/CreateMetaAnalysisSpecificationDialog/CreateMetaAnalysisSpecificationSelectionStep/SelectAnalysesComponent/SelectAnalysesSummaryComponent';
import EditSpecificationDialog from 'components/Dialogs/EditSpecificationDialog/EditSpecificationDialog';
import DisplayMetaAnalysisResult from 'components/DisplayMetaAnalysisResult/DisplayMetaAnalysisResult';
Expand Down Expand Up @@ -126,6 +127,25 @@ const MetaAnalysisPage: React.FC = (props) => {
return `${selectionKey} ${selectionValue}`;
}, [specification]);

const referenceDataset = useMemo(() => {
const isMulti = isMultiGroupAlgorithm({
label: specification?.estimator?.type || '',
description: '',
});

if (isMulti) {
return specification?.conditions?.[1] !== undefined
? specification.conditions[1].toString()
: specification?.database_studyset;
} else {
return null;
}
}, [
specification?.conditions,
specification?.database_studyset,
specification?.estimator?.type,
]);

const metaAnalysisTypeDescription = useMemo(() => {
return getAnalysisTypeDescription((metaAnalysis?.specification as Specification)?.type);
}, [metaAnalysis?.specification]);
Expand Down Expand Up @@ -296,7 +316,7 @@ const MetaAnalysisPage: React.FC = (props) => {
)}

<MetaAnalysisSummaryRow title="selection" value={selectionText}>
{specification?.database_studyset && (
{referenceDataset && (
<>
<SelectAnalysesSummaryComponent
annotationdId={
Expand All @@ -310,7 +330,7 @@ const MetaAnalysisPage: React.FC = (props) => {
}}
/>
<Typography sx={{ marginTop: '1rem', color: 'gray' }}>
Reference Dataset: {specification.database_studyset}
Reference Dataset: {referenceDataset}
</Typography>
</>
)}
Expand Down
2 changes: 2 additions & 0 deletions compose/neurosynth-frontend/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
AnnotationsApi as NeurosynthAnnotationApi,
StudysetsApi as NeurosynthStudysetApi,
ProjectsApi,
DefaultApi as NeurosynthDefaultApi,
} from '../neurosynth-compose-typescript-sdk';

export type NeurostoreAnnotation = AnnotationBase &
Expand Down Expand Up @@ -62,6 +63,7 @@ const NeurosynthServices = {
StudysetsService: new NeurosynthStudysetApi(neurosynthConfig),
AnnotationsService: new NeurosynthAnnotationApi(neurosynthConfig),
ProjectsService: new ProjectsApi(neurosynthConfig),
NeurosynthDefaultApi: new NeurosynthDefaultApi(neurosynthConfig),
};

const UpdateServicesWithToken = (token: string) => {
Expand Down

0 comments on commit 69fe14a

Please sign in to comment.