Skip to content
Snippets Groups Projects
Commit 006067fa authored by Miłosz Grocholewski's avatar Miłosz Grocholewski
Browse files

Merge branch 'feat/vector-map-actions' into 'development'

feat(vector-map): handle user action using only frontend data

See merge request !305
parents 477cd7d9 d34946e2
No related branches found
No related tags found
1 merge request!305feat(vector-map): handle user action using only frontend data
Pipeline #97913 passed
Showing
with 454 additions and 77 deletions
...@@ -2,14 +2,13 @@ ...@@ -2,14 +2,13 @@
import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { Feature } from 'ol';
import { reactionsFixture } from '@/models/fixtures/reactionFixture'; import { reactionsFixture } from '@/models/fixtures/reactionFixture';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { apiPath } from '@/redux/apiPath'; import { apiPath } from '@/redux/apiPath';
import { HttpStatusCode } from 'axios'; import { HttpStatusCode } from 'axios';
import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture';
import { FEATURE_TYPE } from '@/constants/features';
import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction'; import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
const mockedAxiosClient = mockNetworkNewAPIResponse(); const mockedAxiosClient = mockNetworkNewAPIResponse();
jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
...@@ -20,7 +19,6 @@ describe('clickHandleReaction', () => { ...@@ -20,7 +19,6 @@ describe('clickHandleReaction', () => {
let modelId = 1; let modelId = 1;
let reactionId = 1; let reactionId = 1;
const hasFitBounds = true; const hasFitBounds = true;
const feature = new Feature({ type: FEATURE_TYPE.REACTION, id: 1 });
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
...@@ -54,8 +52,13 @@ describe('clickHandleReaction', () => { ...@@ -54,8 +52,13 @@ describe('clickHandleReaction', () => {
) )
.reply(HttpStatusCode.Ok, bioEntityFixture); .reply(HttpStatusCode.Ok, bioEntityFixture);
}); });
await clickHandleReaction(dispatch, hasFitBounds)(feature, modelId); clickHandleReaction(dispatch, hasFitBounds)(
expect(dispatch).toHaveBeenCalledTimes(4); [],
[{ ...newReactionFixture, id: reactionId }],
reactionId,
modelId,
);
expect(dispatch).toHaveBeenCalledTimes(5);
expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId)); expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId));
expect(dispatch).toHaveBeenCalledWith(selectTab('')); expect(dispatch).toHaveBeenCalledWith(selectTab(''));
expect(eventBusDispatchEventSpy).toHaveBeenCalled(); expect(eventBusDispatchEventSpy).toHaveBeenCalled();
...@@ -68,7 +71,7 @@ describe('clickHandleReaction', () => { ...@@ -68,7 +71,7 @@ describe('clickHandleReaction', () => {
unwrap: jest.fn().mockResolvedValue(mockBioEntities), unwrap: jest.fn().mockResolvedValue(mockBioEntities),
})); }));
await clickHandleReaction(dispatch, false)(feature, modelId); clickHandleReaction(dispatch, false)([], [{ ...newReactionFixture, id: 1 }], 1, modelId);
expect(searchFitBounds).not.toHaveBeenCalled(); expect(searchFitBounds).not.toHaveBeenCalled();
}); });
......
import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
import { AppDispatch } from '@/redux/store'; import { AppDispatch } from '@/redux/store';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { BioEntity } from '@/types/models'; import { BioEntity, ModelElement, NewReaction } from '@/types/models';
import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
import { apiPath } from '@/redux/apiPath';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { bioEntitySchema } from '@/models/bioEntitySchema';
import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity';
import { FeatureLike } from 'ol/Feature';
import { getBioEntitiesIdsFromReaction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction';
import { FEATURE_TYPE } from '@/constants/features'; import { FEATURE_TYPE } from '@/constants/features';
import { setMultipleBioEntityContents } from '@/redux/bioEntity/bioEntity.slice';
import { addNumbersToEntityNumberData } from '@/redux/entityNumber/entityNumber.slice';
import { setReactions } from '@/redux/reactions/reactions.slice';
import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction';
import { mapReactionToBioEntity } from '@/utils/bioEntity/mapReactionToBioEntity';
import getModelElementsIdsFromReaction from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction';
import { mapModelElementToBioEntity } from '@/utils/bioEntity/mapModelElementToBioEntity';
/* prettier-ignore */ /* prettier-ignore */
export const clickHandleReaction = export const clickHandleReaction =
(dispatch: AppDispatch, hasFitBounds = false) => (dispatch: AppDispatch, hasFitBounds = false) =>
async (feature: FeatureLike, modelId: number): Promise<void> => { ( modelElements: Array<ModelElement>, reactions: Array<NewReaction>, reactionId: number, modelId: number): void => {
const id = feature.get('id');
const data = await dispatch(getReactionsByIds([id])); const reactionBioEntities: Array<BioEntity> = [];
const payload = data?.payload; const reaction = reactions.find(newReaction => newReaction.id === reactionId);
if (!data || !payload || typeof payload === 'string' || payload.data.length === SIZE_OF_EMPTY_ARRAY) { if(!reaction) {
return; return;
} }
const modelElementsIds = getModelElementsIdsFromReaction(reaction);
modelElementsIds.forEach(modelElementId => {
const modelElement = modelElements.find(element =>
element.id === modelElementId
);
if(!modelElement) {
return;
}
reactionBioEntities.push(mapModelElementToBioEntity(modelElement));
});
const reaction = payload.data[FIRST_ARRAY_ELEMENT]; dispatch(openReactionDrawerById(reactionId));
const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction);
dispatch(openReactionDrawerById(reaction.id));
dispatch(selectTab('')); dispatch(selectTab(''));
const response = await axiosInstanceNewAPI.get<BioEntity>(apiPath.getReactionByIdInNewApi(reaction.id, reaction.modelId)); const bioEntityReaction = mapReactionToBioEntity(reaction);
const isDataValid = validateDataUsingZodSchema(response.data, bioEntitySchema); dispatch(setMultipleBioEntityContents(reactionBioEntities));
dispatch(addNumbersToEntityNumberData(reactionBioEntities.map(reactionBioEntity => reactionBioEntity.elementId)));
if (isDataValid) { dispatch(setReactions([mapNewReactionToReaction(reaction)]));
const reactionNewApi = response.data;
const result = reactionBioEntities.map((bioEntity) => {return { bioEntity, perfect: true };});
const bioEntities = await dispatch( result.push({ bioEntity: bioEntityReaction, perfect: true });
getMultiBioEntityByIds({ PluginsEventBus.dispatchEvent('onSearch', {
elementsToFetch: bioEntitiesIds.map((bioEntityId) => { type: 'reaction',
return { searchValues: [{ id: reactionId, modelId, type: FEATURE_TYPE.REACTION }],
elementId: parseInt(bioEntityId, 10), results: [result]
modelId, });
type: FEATURE_TYPE.ALIAS
}; if (hasFitBounds) {
}) searchFitBounds();
})
).unwrap();
if (bioEntities) {
const result = bioEntities.map((bioEntity) => {return { bioEntity, perfect: true };});
result.push({ bioEntity: reactionNewApi, perfect: true });
PluginsEventBus.dispatchEvent('onSearch', {
type: 'reaction',
searchValues: [{ id, modelId, type: FEATURE_TYPE.REACTION }],
results: [result]
});
if (hasFitBounds) {
searchFitBounds();
}
}
} }
}; };
/* eslint-disable no-magic-numbers */
import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
import getModelElementsIdsFromReaction from './getModelElementsIdsFromReaction';
describe('getModelElementsIdsFromReaction', () => {
it('should return correct model elements ids from given reaction', () => {
const result = getModelElementsIdsFromReaction(newReactionFixture);
expect(result).toEqual([
...newReactionFixture.products.map(product => product.element),
...newReactionFixture.reactants.map(reactant => reactant.element),
...newReactionFixture.modifiers.map(modifier => modifier.element),
]);
});
it('should return empty array', () => {
const result = getModelElementsIdsFromReaction({
...newReactionFixture,
products: [],
reactants: [],
modifiers: [],
});
expect(result).toEqual([]);
});
});
import { NewReaction } from '@/types/models';
export default function getModelElementsIdsFromReaction(reaction: NewReaction): Array<number> {
return [...reaction.products, ...reaction.reactants, ...reaction.modifiers].map(
bioEntity => bioEntity.element,
);
}
...@@ -59,6 +59,8 @@ describe('onMapLeftClick', () => { ...@@ -59,6 +59,8 @@ describe('onMapLeftClick', () => {
dispatch, dispatch,
isResultDrawerOpen, isResultDrawerOpen,
comments, comments,
[],
[],
)(event, mapInstance); )(event, mapInstance);
expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object))); expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object)));
...@@ -84,6 +86,8 @@ describe('onMapLeftClick', () => { ...@@ -84,6 +86,8 @@ describe('onMapLeftClick', () => {
dispatch, dispatch,
isResultDrawerOpen, isResultDrawerOpen,
comments, comments,
[],
[],
)(event, mapInstance); )(event, mapInstance);
expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch); expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
...@@ -106,6 +110,8 @@ describe('onMapLeftClick', () => { ...@@ -106,6 +110,8 @@ describe('onMapLeftClick', () => {
dispatch, dispatch,
isResultDrawerOpen, isResultDrawerOpen,
comments, comments,
[],
[],
)(event, mapInstance); )(event, mapInstance);
expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch); expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
......
import { MapSize } from '@/redux/map/map.types'; import { MapSize } from '@/redux/map/map.types';
import { AppDispatch } from '@/redux/store'; import { AppDispatch } from '@/redux/store';
import { Map, MapBrowserEvent } from 'ol'; import { Map, MapBrowserEvent } from 'ol';
import { Comment } from '@/types/models'; import { Comment, ModelElement, NewReaction } from '@/types/models';
import { updateLastClick } from '@/redux/map/map.slice'; import { updateLastClick } from '@/redux/map/map.slice';
import { toLonLat } from 'ol/proj'; import { toLonLat } from 'ol/proj';
import { latLngToPoint } from '@/utils/map/latLngToPoint'; import { latLngToPoint } from '@/utils/map/latLngToPoint';
...@@ -17,7 +17,7 @@ import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/ ...@@ -17,7 +17,7 @@ import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/
/* prettier-ignore */ /* prettier-ignore */
export const onMapLeftClick = export const onMapLeftClick =
(mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[]) => (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[], modelElements: Array<ModelElement>, reactions: Array<NewReaction>) =>
async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
const [lng, lat] = toLonLat(coordinate); const [lng, lat] = toLonLat(coordinate);
const point = latLngToPoint([lat, lng], mapSize); const point = latLngToPoint([lat, lng], mapSize);
...@@ -52,9 +52,10 @@ export const onMapLeftClick = ...@@ -52,9 +52,10 @@ export const onMapLeftClick =
dispatch(handleDataReset); dispatch(handleDataReset);
const type = featureAtPixel.get('type'); const type = featureAtPixel.get('type');
const id = featureAtPixel.get('id');
if(type === FEATURE_TYPE.ALIAS) { if(type === FEATURE_TYPE.ALIAS) {
await leftClickHandleAlias(dispatch)(featureAtPixel, modelId); await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
} else if (type === FEATURE_TYPE.REACTION) { } else if (type === FEATURE_TYPE.REACTION) {
await clickHandleReaction(dispatch)(featureAtPixel, modelId); clickHandleReaction(dispatch)(modelElements, reactions, id, modelId);
} }
}; };
...@@ -9,7 +9,8 @@ import VectorLayer from 'ol/layer/Vector'; ...@@ -9,7 +9,8 @@ import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector'; import VectorSource from 'ol/source/Vector';
import { Feature } from 'ol'; import { Feature } from 'ol';
import { FEATURE_TYPE } from '@/constants/features'; import { FEATURE_TYPE } from '@/constants/features';
import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture'; import { modelElementFixture } from '@/models/fixtures/modelElementFixture';
import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import * as rightClickHandleAlias from './rightClickHandleAlias'; import * as rightClickHandleAlias from './rightClickHandleAlias';
import * as clickHandleReaction from '../clickHandleReaction'; import * as clickHandleReaction from '../clickHandleReaction';
...@@ -52,7 +53,6 @@ describe('onMapRightClick', () => { ...@@ -52,7 +53,6 @@ describe('onMapRightClick', () => {
it('calls rightClickHandleAlias if feature type is ALIAS', async () => { it('calls rightClickHandleAlias if feature type is ALIAS', async () => {
const dispatch = jest.fn(); const dispatch = jest.fn();
const modelElement = modelElementsFixture.content[0];
jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => { jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => {
return [vectorLayer]; return [vectorLayer];
}); });
...@@ -63,9 +63,15 @@ describe('onMapRightClick', () => { ...@@ -63,9 +63,15 @@ describe('onMapRightClick', () => {
return vectorSource; return vectorSource;
}); });
jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => { jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
return new Feature({ id: modelElement.id, type: FEATURE_TYPE.ALIAS }); return new Feature({ id: modelElementFixture.id, type: FEATURE_TYPE.ALIAS });
}); });
await onMapRightClick(mapSize, modelId, dispatch, [modelElement])(event, mapInstance); await onMapRightClick(
mapSize,
modelId,
dispatch,
[modelElementFixture],
[],
)(event, mapInstance);
expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object))); expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel)); expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
...@@ -86,7 +92,13 @@ describe('onMapRightClick', () => { ...@@ -86,7 +92,13 @@ describe('onMapRightClick', () => {
jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => { jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
return new Feature({ id: 1, type: FEATURE_TYPE.REACTION }); return new Feature({ id: 1, type: FEATURE_TYPE.REACTION });
}); });
await onMapRightClick(mapSize, modelId, dispatch, [])(event, mapInstance); await onMapRightClick(
mapSize,
modelId,
dispatch,
[],
[{ ...newReactionFixture, id: 1 }],
)(event, mapInstance);
expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object))); expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel)); expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
......
...@@ -9,14 +9,14 @@ import { FEATURE_TYPE } from '@/constants/features'; ...@@ -9,14 +9,14 @@ import { FEATURE_TYPE } from '@/constants/features';
import VectorLayer from 'ol/layer/Vector'; import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector'; import VectorSource from 'ol/source/Vector';
import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice'; import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
import { ModelElement } from '@/types/models'; import { ModelElement, NewReaction } from '@/types/models';
import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias'; import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias';
import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction'; import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
/* prettier-ignore */ /* prettier-ignore */
export const onMapRightClick = export const onMapRightClick =
(mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>) => (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>, reactions: Array<NewReaction>) =>
async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
const [lng, lat] = toLonLat(coordinate); const [lng, lat] = toLonLat(coordinate);
...@@ -51,6 +51,6 @@ export const onMapRightClick = ...@@ -51,6 +51,6 @@ export const onMapRightClick =
} }
await rightClickHandleAlias(dispatch)(id, modelElement); await rightClickHandleAlias(dispatch)(id, modelElement);
} else if (type === FEATURE_TYPE.REACTION) { } else if (type === FEATURE_TYPE.REACTION) {
await clickHandleReaction(dispatch)(foundFeature, modelId); clickHandleReaction(dispatch)(modelElements, reactions, id, modelId);
} }
}; };
...@@ -15,6 +15,7 @@ import { Coordinate } from 'ol/coordinate'; ...@@ -15,6 +15,7 @@ import { Coordinate } from 'ol/coordinate';
import { Pixel } from 'ol/pixel'; import { Pixel } from 'ol/pixel';
import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick'; import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector'; import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
interface UseOlMapVectorListenersInput { interface UseOlMapVectorListenersInput {
mapInstance: MapInstance; mapInstance: MapInstance;
...@@ -25,6 +26,7 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners ...@@ -25,6 +26,7 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
const modelId = useSelector(currentModelIdSelector); const modelId = useSelector(currentModelIdSelector);
const isResultDrawerOpen = useSelector(resultDrawerOpen); const isResultDrawerOpen = useSelector(resultDrawerOpen);
const modelElements = useSelector(modelElementsSelector); const modelElements = useSelector(modelElementsSelector);
const reactions = useSelector(newReactionsDataSelector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const coordinate = useRef<Coordinate>([]); const coordinate = useRef<Coordinate>([]);
const pixel = useRef<Pixel>([]); const pixel = useRef<Pixel>([]);
...@@ -33,13 +35,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners ...@@ -33,13 +35,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
const vectorRendering = useAppSelector(vectorRenderingSelector); const vectorRendering = useAppSelector(vectorRenderingSelector);
const handleMapLeftClick = useDebouncedCallback( const handleMapLeftClick = useDebouncedCallback(
onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments), onMapLeftClick(
mapSize,
modelId,
dispatch,
isResultDrawerOpen,
comments,
modelElements?.content || [],
reactions,
),
OPTIONS.clickPersistTime, OPTIONS.clickPersistTime,
{ leading: false }, { leading: false },
); );
const handleRightClick = useDebouncedCallback( const handleRightClick = useDebouncedCallback(
onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || []), onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || [], reactions),
OPTIONS.clickPersistTime, OPTIONS.clickPersistTime,
{ {
leading: false, leading: false,
......
import { DEFAULT_ERROR } from '@/constants/errors'; import { DEFAULT_ERROR } from '@/constants/errors';
import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
import { getBioEntityById } from '@/redux/bioEntity/thunks/getBioEntity'; import { getBioEntityById } from '@/redux/bioEntity/thunks/getBioEntity';
import { BioEntityContent } from '@/types/models'; import { BioEntity, BioEntityContent } from '@/types/models';
import { BIOENTITY_SUBMAP_CONNECTIONS_INITIAL_STATE } from './bioEntity.constants'; import { BIOENTITY_SUBMAP_CONNECTIONS_INITIAL_STATE } from './bioEntity.constants';
import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks'; import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks';
import { BioEntityContentsState } from './bioEntity.types'; import { BioEntityContentsState } from './bioEntity.types';
...@@ -136,3 +136,23 @@ export const setBioEntityContentsReducer = ( ...@@ -136,3 +136,23 @@ export const setBioEntityContentsReducer = (
]; ];
state.loading = 'succeeded'; state.loading = 'succeeded';
}; };
export const setMultipleBioEntityContentsReducer = (
state: BioEntityContentsState,
action: PayloadAction<Array<BioEntity>>,
): void => {
state.data = [
{
data: action.payload.map(bioEntity => {
return {
bioEntity,
perfect: true,
};
}),
loading: 'succeeded',
error: DEFAULT_ERROR,
searchQueryElement: 'asd',
},
];
state.loading = 'succeeded';
};
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
getMultiBioEntityContentsReducer, getMultiBioEntityContentsReducer,
getSubmapConnectionsBioEntityReducer, getSubmapConnectionsBioEntityReducer,
setBioEntityContentsReducer, setBioEntityContentsReducer,
setMultipleBioEntityContentsReducer,
toggleIsContentTabOpenedReducer, toggleIsContentTabOpenedReducer,
} from './bioEntity.reducers'; } from './bioEntity.reducers';
...@@ -16,6 +17,7 @@ export const bioEntityContentsSlice = createSlice({ ...@@ -16,6 +17,7 @@ export const bioEntityContentsSlice = createSlice({
clearBioEntitiesData: clearBioEntitiesDataReducer, clearBioEntitiesData: clearBioEntitiesDataReducer,
toggleIsContentTabOpened: toggleIsContentTabOpenedReducer, toggleIsContentTabOpened: toggleIsContentTabOpenedReducer,
setBioEntityContents: setBioEntityContentsReducer, setBioEntityContents: setBioEntityContentsReducer,
setMultipleBioEntityContents: setMultipleBioEntityContentsReducer,
}, },
extraReducers: builder => { extraReducers: builder => {
getBioEntityContentsReducer(builder); getBioEntityContentsReducer(builder);
...@@ -24,7 +26,11 @@ export const bioEntityContentsSlice = createSlice({ ...@@ -24,7 +26,11 @@ export const bioEntityContentsSlice = createSlice({
}, },
}); });
export const { clearBioEntitiesData, toggleIsContentTabOpened, setBioEntityContents } = export const {
bioEntityContentsSlice.actions; clearBioEntitiesData,
toggleIsContentTabOpened,
setBioEntityContents,
setMultipleBioEntityContents,
} = bioEntityContentsSlice.actions;
export default bioEntityContentsSlice.reducer; export default bioEntityContentsSlice.reducer;
...@@ -20,7 +20,6 @@ export const addNumbersToEntityNumberDataReducer = ( ...@@ -20,7 +20,6 @@ export const addNumbersToEntityNumberDataReducer = (
const newEntityNumber: EntityNumber = Object.fromEntries( const newEntityNumber: EntityNumber = Object.fromEntries(
uniqueIds.map((id, index) => [id, lastNumber + index]), uniqueIds.map((id, index) => [id, lastNumber + index]),
); );
state.data = { state.data = {
...newEntityNumber, ...newEntityNumber,
...state.data, ...state.data,
......
import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
import { Reaction } from '@/types/models';
import { REACTIONS_INITIAL_STATE } from './reactions.constants'; import { REACTIONS_INITIAL_STATE } from './reactions.constants';
import { getReactionsByIds } from './reactions.thunks'; import { getReactionsByIds } from './reactions.thunks';
import { ReactionsState } from './reactions.types'; import { ReactionsState } from './reactions.types';
...@@ -26,3 +27,11 @@ export const resetReactionsDataReducer = (state: ReactionsState): void => { ...@@ -26,3 +27,11 @@ export const resetReactionsDataReducer = (state: ReactionsState): void => {
state.error = REACTIONS_INITIAL_STATE.error; state.error = REACTIONS_INITIAL_STATE.error;
state.loading = REACTIONS_INITIAL_STATE.loading; state.loading = REACTIONS_INITIAL_STATE.loading;
}; };
export const setReactionsReducer = (
state: ReactionsState,
action: PayloadAction<Array<Reaction>>,
): void => {
state.data = action.payload;
state.loading = 'succeeded';
};
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { REACTIONS_INITIAL_STATE } from './reactions.constants'; import { REACTIONS_INITIAL_STATE } from './reactions.constants';
import { getReactionsReducer, resetReactionsDataReducer } from './reactions.reducers'; import {
getReactionsReducer,
resetReactionsDataReducer,
setReactionsReducer,
} from './reactions.reducers';
export const reactionsSlice = createSlice({ export const reactionsSlice = createSlice({
name: 'reactions', name: 'reactions',
initialState: REACTIONS_INITIAL_STATE, initialState: REACTIONS_INITIAL_STATE,
reducers: { reducers: {
resetReactionsData: resetReactionsDataReducer, resetReactionsData: resetReactionsDataReducer,
setReactions: setReactionsReducer,
}, },
extraReducers: builder => { extraReducers: builder => {
getReactionsReducer(builder); getReactionsReducer(builder);
}, },
}); });
export const { resetReactionsData } = reactionsSlice.actions; export const { resetReactionsData, setReactions } = reactionsSlice.actions;
export default reactionsSlice.reducer; export default reactionsSlice.reducer;
import { BioEntity, NewReaction } from '@/types/models';
export function mapReactionToBioEntity(reaction: NewReaction): BioEntity {
return {
id: reaction.id,
name: reaction.name,
model: reaction.model,
elementId: reaction.elementId,
references: reaction.references,
z: reaction.z,
notes: reaction.notes,
symbol: reaction.symbol,
visibilityLevel: reaction.visibilityLevel,
synonyms: reaction.synonyms,
abbreviation: reaction.abbreviation,
formula: reaction.formula,
sboTerm: reaction.sboTerm,
} as BioEntity;
}
enum ReactionTypeEnum {
'SBO:0000013' = 'Catalysis',
'SBO:0000180' = 'Dissociation',
'SBO:0000177' = 'Heterodimer association',
'SBO:0000537' = 'Inhibition',
'SBO:0000205' = 'Known Transition omitted',
'SBO:0000594' = 'Modulation',
'SBO:0000407' = 'Negative influence',
'SBO:0000459' = 'Physical stimulation',
'SBO:0000171' = 'Positive influence',
'SBO:0000632' = 'Reduced modulation',
'SBO:0000411' = 'Reduced physical stimulation',
'SBO:0000533' = 'Reduced trigger',
'SBO:0000176' = 'State transition',
'SBO:0000183' = 'Transcription',
'SBO:0000184' = 'Translation',
'SBO:0000185' = 'Transport',
'SBO:0000461' = 'Trigger',
'SBO:0000178' = 'Truncation',
'SBO:0000462' = 'Unknown catalysis',
'SBO:0000536' = 'Unknown inhibition',
'SBO:0000169' = 'Unknown negative influence',
'SBO:0000172' = 'Unknown positive influence',
'SBO:0000631' = 'Unknown reduced modulation',
'SBO:0000170' = 'Unknown reduced physical stimulation',
'SBO:0000534' = 'Unknown reduced trigger',
'SBO:0000396' = 'Unknown transition',
}
export default ReactionTypeEnum;
/* eslint-disable no-magic-numbers */
import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction';
describe('mapNewReactionToReaction', () => {
const newReaction = {
id: 31141,
notes: '',
idReaction: 're22',
name: '',
reversible: false,
symbol: null,
abbreviation: null,
formula: null,
mechanicalConfidenceScore: null,
lowerBound: null,
upperBound: null,
subsystem: null,
geneProteinReaction: null,
visibilityLevel: '',
z: 45,
synonyms: [],
model: 137,
kinetics: null,
line: {
id: 109668,
width: 1,
color: {
alpha: 255,
rgb: -16777216,
},
z: 0,
segments: [
{
x1: 149.31765717927775,
y1: 319.13724818355684,
x2: 142.5553586937381,
y2: 314.86275181644316,
},
],
startArrow: {
arrowType: 'NONE',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
endArrow: {
arrowType: 'NONE',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
lineType: 'SOLID',
},
processCoordinates: null,
modifiers: [],
products: [
{
id: 85169,
line: {
id: 109670,
width: 1,
color: {
alpha: 255,
rgb: -16777216,
},
z: 0,
segments: [
{
x1: 142.5553586937381,
y1: 314.86275181644316,
x2: 122.2063492063492,
y2: 302,
},
],
startArrow: {
arrowType: 'NONE',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
endArrow: {
arrowType: 'OPEN',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
lineType: 'SOLID',
},
stoichiometry: null,
element: 58886,
},
],
reactants: [
{
id: 85168,
line: {
id: 109669,
width: 1,
color: {
alpha: 255,
rgb: -16777216,
},
z: 0,
segments: [
{
x1: 169.66666666666666,
y1: 332,
x2: 149.31765717927775,
y2: 319.13724818355684,
},
],
startArrow: {
arrowType: 'NONE',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
endArrow: {
arrowType: 'NONE',
angle: 2.748893571891069,
lineType: 'SOLID',
length: 15,
},
lineType: 'SOLID',
},
stoichiometry: null,
element: 58872,
},
],
operators: [],
elementId: 're22',
references: [],
sboTerm: 'SBO:0000171',
};
const expectedReaction = {
centerPoint: {
x: 0,
y: 0,
},
hierarchyVisibilityLevel: '',
id: 31141,
kineticLaw: null,
lines: [
{
start: {
x: 149.31765717927775,
y: 319.13724818355684,
},
end: {
x: 142.5553586937381,
y: 314.86275181644316,
},
type: '',
},
{
start: {
x: 142.5553586937381,
y: 314.86275181644316,
},
end: {
x: 122.2063492063492,
y: 302,
},
type: '',
},
{
start: {
x: 169.66666666666666,
y: 332,
},
end: {
x: 149.31765717927775,
y: 319.13724818355684,
},
type: '',
},
],
modelId: 137,
modifiers: [],
name: '',
notes: '',
products: [],
reactants: [],
reactionId: 're22',
references: [],
type: 'Positive influence',
};
it('should return correct reaction object from new reaction object', () => {
const result = mapNewReactionToReaction(newReaction);
expect(result).toEqual(expectedReaction);
});
});
/* eslint-disable no-magic-numbers */
import { NewReaction, Reaction, ReactionLine } from '@/types/models';
import ReactionTypeEnum from '@/utils/reaction/ReactionTypeEnum';
type ReactionTypeKey = keyof typeof ReactionTypeEnum;
export default function mapNewReactionToReaction(newReaction: NewReaction): Reaction {
const lines: Array<ReactionLine> = [];
let start;
let end;
newReaction.line.segments.forEach(segment => {
start = { x: segment.x1, y: segment.y1 };
end = { x: segment.x2, y: segment.y2 };
lines.push({ start, end, type: '' });
});
[
...newReaction.products,
...newReaction.reactants,
...newReaction.modifiers,
...newReaction.operators,
].forEach(element => {
element.line.segments.forEach(segment => {
start = { x: segment.x1, y: segment.y1 };
end = { x: segment.x2, y: segment.y2 };
lines.push({ start, end, type: '' });
});
});
return {
centerPoint: { x: 0, y: 0 },
hierarchyVisibilityLevel: '',
id: newReaction.id,
kineticLaw: null,
lines,
modelId: newReaction.model,
modifiers: [],
name: '',
notes: newReaction.notes,
products: [],
reactants: [],
reactionId: newReaction.idReaction,
references: newReaction.references,
type: ReactionTypeEnum[newReaction.sboTerm as ReactionTypeKey],
};
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment