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 @@
import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { Feature } from 'ol';
import { reactionsFixture } from '@/models/fixtures/reactionFixture';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { apiPath } from '@/redux/apiPath';
import { HttpStatusCode } from 'axios';
import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture';
import { FEATURE_TYPE } from '@/constants/features';
import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
const mockedAxiosClient = mockNetworkNewAPIResponse();
jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
......@@ -20,7 +19,6 @@ describe('clickHandleReaction', () => {
let modelId = 1;
let reactionId = 1;
const hasFitBounds = true;
const feature = new Feature({ type: FEATURE_TYPE.REACTION, id: 1 });
beforeEach(() => {
jest.clearAllMocks();
......@@ -54,8 +52,13 @@ describe('clickHandleReaction', () => {
)
.reply(HttpStatusCode.Ok, bioEntityFixture);
});
await clickHandleReaction(dispatch, hasFitBounds)(feature, modelId);
expect(dispatch).toHaveBeenCalledTimes(4);
clickHandleReaction(dispatch, hasFitBounds)(
[],
[{ ...newReactionFixture, id: reactionId }],
reactionId,
modelId,
);
expect(dispatch).toHaveBeenCalledTimes(5);
expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId));
expect(dispatch).toHaveBeenCalledWith(selectTab(''));
expect(eventBusDispatchEventSpy).toHaveBeenCalled();
......@@ -68,7 +71,7 @@ describe('clickHandleReaction', () => {
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
await clickHandleReaction(dispatch, false)(feature, modelId);
clickHandleReaction(dispatch, false)([], [{ ...newReactionFixture, id: 1 }], 1, modelId);
expect(searchFitBounds).not.toHaveBeenCalled();
});
......
import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
import { AppDispatch } from '@/redux/store';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { BioEntity } 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 { BioEntity, ModelElement, NewReaction } from '@/types/models';
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 */
export const clickHandleReaction =
(dispatch: AppDispatch, hasFitBounds = false) =>
async (feature: FeatureLike, modelId: number): Promise<void> => {
const id = feature.get('id');
const data = await dispatch(getReactionsByIds([id]));
const payload = data?.payload;
if (!data || !payload || typeof payload === 'string' || payload.data.length === SIZE_OF_EMPTY_ARRAY) {
( modelElements: Array<ModelElement>, reactions: Array<NewReaction>, reactionId: number, modelId: number): void => {
const reactionBioEntities: Array<BioEntity> = [];
const reaction = reactions.find(newReaction => newReaction.id === reactionId);
if(!reaction) {
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];
const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction);
dispatch(openReactionDrawerById(reaction.id));
dispatch(openReactionDrawerById(reactionId));
dispatch(selectTab(''));
const response = await axiosInstanceNewAPI.get<BioEntity>(apiPath.getReactionByIdInNewApi(reaction.id, reaction.modelId));
const isDataValid = validateDataUsingZodSchema(response.data, bioEntitySchema);
if (isDataValid) {
const reactionNewApi = response.data;
const bioEntities = await dispatch(
getMultiBioEntityByIds({
elementsToFetch: bioEntitiesIds.map((bioEntityId) => {
return {
elementId: parseInt(bioEntityId, 10),
modelId,
type: FEATURE_TYPE.ALIAS
};
})
})
).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();
}
}
const bioEntityReaction = mapReactionToBioEntity(reaction);
dispatch(setMultipleBioEntityContents(reactionBioEntities));
dispatch(addNumbersToEntityNumberData(reactionBioEntities.map(reactionBioEntity => reactionBioEntity.elementId)));
dispatch(setReactions([mapNewReactionToReaction(reaction)]));
const result = reactionBioEntities.map((bioEntity) => {return { bioEntity, perfect: true };});
result.push({ bioEntity: bioEntityReaction, perfect: true });
PluginsEventBus.dispatchEvent('onSearch', {
type: 'reaction',
searchValues: [{ id: reactionId, 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', () => {
dispatch,
isResultDrawerOpen,
comments,
[],
[],
)(event, mapInstance);
expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object)));
......@@ -84,6 +86,8 @@ describe('onMapLeftClick', () => {
dispatch,
isResultDrawerOpen,
comments,
[],
[],
)(event, mapInstance);
expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
......@@ -106,6 +110,8 @@ describe('onMapLeftClick', () => {
dispatch,
isResultDrawerOpen,
comments,
[],
[],
)(event, mapInstance);
expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
......
import { MapSize } from '@/redux/map/map.types';
import { AppDispatch } from '@/redux/store';
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 { toLonLat } from 'ol/proj';
import { latLngToPoint } from '@/utils/map/latLngToPoint';
......@@ -17,7 +17,7 @@ import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/
/* prettier-ignore */
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> => {
const [lng, lat] = toLonLat(coordinate);
const point = latLngToPoint([lat, lng], mapSize);
......@@ -52,9 +52,10 @@ export const onMapLeftClick =
dispatch(handleDataReset);
const type = featureAtPixel.get('type');
const id = featureAtPixel.get('id');
if(type === FEATURE_TYPE.ALIAS) {
await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
} 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';
import VectorSource from 'ol/source/Vector';
import { Feature } from 'ol';
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 * as rightClickHandleAlias from './rightClickHandleAlias';
import * as clickHandleReaction from '../clickHandleReaction';
......@@ -52,7 +53,6 @@ describe('onMapRightClick', () => {
it('calls rightClickHandleAlias if feature type is ALIAS', async () => {
const dispatch = jest.fn();
const modelElement = modelElementsFixture.content[0];
jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => {
return [vectorLayer];
});
......@@ -63,9 +63,15 @@ describe('onMapRightClick', () => {
return vectorSource;
});
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(openContextMenu(event.pixel));
......@@ -86,7 +92,13 @@ describe('onMapRightClick', () => {
jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
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(openContextMenu(event.pixel));
......
......@@ -9,14 +9,14 @@ import { FEATURE_TYPE } from '@/constants/features';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
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 { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
/* prettier-ignore */
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> => {
const [lng, lat] = toLonLat(coordinate);
......@@ -51,6 +51,6 @@ export const onMapRightClick =
}
await rightClickHandleAlias(dispatch)(id, modelElement);
} 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';
import { Pixel } from 'ol/pixel';
import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
interface UseOlMapVectorListenersInput {
mapInstance: MapInstance;
......@@ -25,6 +26,7 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
const modelId = useSelector(currentModelIdSelector);
const isResultDrawerOpen = useSelector(resultDrawerOpen);
const modelElements = useSelector(modelElementsSelector);
const reactions = useSelector(newReactionsDataSelector);
const dispatch = useAppDispatch();
const coordinate = useRef<Coordinate>([]);
const pixel = useRef<Pixel>([]);
......@@ -33,13 +35,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
const vectorRendering = useAppSelector(vectorRenderingSelector);
const handleMapLeftClick = useDebouncedCallback(
onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments),
onMapLeftClick(
mapSize,
modelId,
dispatch,
isResultDrawerOpen,
comments,
modelElements?.content || [],
reactions,
),
OPTIONS.clickPersistTime,
{ leading: false },
);
const handleRightClick = useDebouncedCallback(
onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || []),
onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || [], reactions),
OPTIONS.clickPersistTime,
{
leading: false,
......
import { DEFAULT_ERROR } from '@/constants/errors';
import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
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 { getBioEntity, getMultiBioEntity } from './bioEntity.thunks';
import { BioEntityContentsState } from './bioEntity.types';
......@@ -136,3 +136,23 @@ export const setBioEntityContentsReducer = (
];
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 {
getMultiBioEntityContentsReducer,
getSubmapConnectionsBioEntityReducer,
setBioEntityContentsReducer,
setMultipleBioEntityContentsReducer,
toggleIsContentTabOpenedReducer,
} from './bioEntity.reducers';
......@@ -16,6 +17,7 @@ export const bioEntityContentsSlice = createSlice({
clearBioEntitiesData: clearBioEntitiesDataReducer,
toggleIsContentTabOpened: toggleIsContentTabOpenedReducer,
setBioEntityContents: setBioEntityContentsReducer,
setMultipleBioEntityContents: setMultipleBioEntityContentsReducer,
},
extraReducers: builder => {
getBioEntityContentsReducer(builder);
......@@ -24,7 +26,11 @@ export const bioEntityContentsSlice = createSlice({
},
});
export const { clearBioEntitiesData, toggleIsContentTabOpened, setBioEntityContents } =
bioEntityContentsSlice.actions;
export const {
clearBioEntitiesData,
toggleIsContentTabOpened,
setBioEntityContents,
setMultipleBioEntityContents,
} = bioEntityContentsSlice.actions;
export default bioEntityContentsSlice.reducer;
......@@ -20,7 +20,6 @@ export const addNumbersToEntityNumberDataReducer = (
const newEntityNumber: EntityNumber = Object.fromEntries(
uniqueIds.map((id, index) => [id, lastNumber + index]),
);
state.data = {
...newEntityNumber,
...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 { getReactionsByIds } from './reactions.thunks';
import { ReactionsState } from './reactions.types';
......@@ -26,3 +27,11 @@ export const resetReactionsDataReducer = (state: ReactionsState): void => {
state.error = REACTIONS_INITIAL_STATE.error;
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 { REACTIONS_INITIAL_STATE } from './reactions.constants';
import { getReactionsReducer, resetReactionsDataReducer } from './reactions.reducers';
import {
getReactionsReducer,
resetReactionsDataReducer,
setReactionsReducer,
} from './reactions.reducers';
export const reactionsSlice = createSlice({
name: 'reactions',
initialState: REACTIONS_INITIAL_STATE,
reducers: {
resetReactionsData: resetReactionsDataReducer,
setReactions: setReactionsReducer,
},
extraReducers: builder => {
getReactionsReducer(builder);
},
});
export const { resetReactionsData } = reactionsSlice.actions;
export const { resetReactionsData, setReactions } = reactionsSlice.actions;
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