diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6c2ae75b0f740a6b1433506e0d17e19abc9533f --- /dev/null +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction.ts @@ -0,0 +1,9 @@ +import { Reaction } from '@/types/models'; + +export const getBioEntitiesIdsFromReaction = (reaction: Reaction): string[] => { + const { products, reactants, modifiers } = reaction; + const productsIds = products.map(p => p.aliasId); + const reactantsIds = reactants.map(r => r.aliasId); + const modifiersIds = modifiers.map(m => m.aliasId); + return [...productsIds, ...reactantsIds, ...modifiersIds].map(identifier => String(identifier)); +}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts index 58ee8bf0e88ac60f71eb84361c6c67a55aed8c4e..61e8c191fbab114e5c4a850266bae0ef0b390ebe 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts @@ -83,12 +83,18 @@ describe('handleReactionResults - util', () => { it('should run getBioEntityContents fullfilled as fourth action', () => { const actions = store.getActions(); expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[5].type).toEqual('project/getBioEntityContents/fulfilled'); + expect(actions[5].type).toEqual('reactions/getByIds/pending'); }); - it('should run addNumbersToEntityNumberData as fifth action', () => { + it('should run getBioEntityContents fullfilled as fourth action', () => { + const actions = store.getActions(); + expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); + expect(actions[6].type).toEqual('project/getBioEntityContents/fulfilled'); + }); + + it('should run getBioEntityContents fullfilled as fourth action', () => { const actions = store.getActions(); expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[6].type).toEqual('entityNumber/addNumbersToEntityNumberData'); + expect(actions[7].type).toEqual('entityNumber/addNumbersToEntityNumberData'); }); }); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts index 564f106be15fbee31cff1611ae95a0228542f89a..bfbcaaa362c7350ed2d53fdb861f39fdd0b52824 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts @@ -5,25 +5,21 @@ 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 { ElementSearchResult, Reaction } from '@/types/models'; -import { PayloadAction } from '@reduxjs/toolkit'; +import { ElementSearchResult } from '@/types/models'; +import { getBioEntitiesIdsFromReaction } from './getBioEntitiesIdsFromReaction'; /* prettier-ignore */ export const handleReactionResults = (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) => async ({ id }: ElementSearchResult): Promise<void> => { - const data = await dispatch(getReactionsByIds([id])) as PayloadAction<Reaction[] | undefined>; + const data = await dispatch(getReactionsByIds([id])); const payload = data?.payload; - if (!data || !payload || payload.length === SIZE_OF_EMPTY_ARRAY) { + if (!data || !payload || typeof payload === "string" || payload.data.length === SIZE_OF_EMPTY_ARRAY) { return; } - const reaction = payload[FIRST_ARRAY_ELEMENT]; - const { products, reactants, modifiers } = reaction; - const productsIds = products.map(p => p.aliasId); - const reactantsIds = reactants.map(r => r.aliasId); - const modifiersIds = modifiers.map(m => m.aliasId); - const bioEntitiesIds = [...productsIds, ...reactantsIds, ...modifiersIds].map(identifier => String(identifier)); + const reaction = payload.data[FIRST_ARRAY_ELEMENT]; + const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction); dispatch(openReactionDrawerById(reaction.id)); await dispatch( diff --git a/src/redux/bioEntity/bioEntity.thunks.ts b/src/redux/bioEntity/bioEntity.thunks.ts index 783735232b4016cd7823ae5805755b1a8a8dc9d8..18fa812c93182be6346b9bff5f77a9168308a870 100644 --- a/src/redux/bioEntity/bioEntity.thunks.ts +++ b/src/redux/bioEntity/bioEntity.thunks.ts @@ -1,106 +1,4 @@ -import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; -import { apiPath } from '@/redux/apiPath'; -import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; -import { BioEntityContent, BioEntityResponse } from '@/types/models'; -import { PerfectMultiSearchParams, PerfectSearchParams } from '@/types/search'; -import { ThunkConfig } from '@/types/store'; -import { getErrorMessage } from '@/utils/getErrorMessage'; -import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { PayloadAction, createAsyncThunk } from '@reduxjs/toolkit'; -import { addNumbersToEntityNumberData } from '../entityNumber/entityNumber.slice'; -import { getReactionsByIds } from '../reactions/reactions.thunks'; -import { - BIO_ENTITY_FETCHING_ERROR_PREFIX, - MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX, -} from './bioEntity.constants'; +import { getBioEntity } from './thunks/getBioEntity'; +import { getMultiBioEntity } from './thunks/getMultiBioEntity'; -type GetBioEntityProps = PerfectSearchParams; - -export const getBioEntity = createAsyncThunk< - BioEntityContent[] | undefined, - GetBioEntityProps, - ThunkConfig ->( - 'project/getBioEntityContents', - async ( - { searchQuery, isPerfectMatch, addNumbersToEntityNumber = true }, - { rejectWithValue, dispatch }, - ) => { - try { - const response = await axiosInstanceNewAPI.get<BioEntityResponse>( - apiPath.getBioEntityContentsStringWithQuery({ searchQuery, isPerfectMatch }), - ); - - const isDataValidBioEnity = validateDataUsingZodSchema( - response.data, - bioEntityResponseSchema, - ); - - const bioEntityReactionsIds = (response.data?.content || []) - .filter(c => c?.bioEntity?.idReaction) - .map(c => c?.bioEntity?.id) - .filter((id): id is number => typeof id === 'number'); - - dispatch( - getReactionsByIds({ - ids: bioEntityReactionsIds, - shouldConcat: true, - }), - ); - - if (addNumbersToEntityNumber && response.data.content) { - const bioEntityIds = response.data.content.map(b => b.bioEntity.elementId); - dispatch(addNumbersToEntityNumberData(bioEntityIds)); - } - - return isDataValidBioEnity ? response.data.content : undefined; - } catch (error) { - const errorMessage = getErrorMessage({ - error, - prefix: BIO_ENTITY_FETCHING_ERROR_PREFIX, - }); - return rejectWithValue(errorMessage); - } - }, -); - -type GetMultiBioEntityProps = PerfectMultiSearchParams; -type GetMultiBioEntityActions = PayloadAction<BioEntityContent[] | undefined | string>[]; // if error thrown, string containing error message is returned - -export const getMultiBioEntity = createAsyncThunk< - BioEntityContent[], - GetMultiBioEntityProps, - ThunkConfig ->( - 'project/getMultiBioEntity', - // eslint-disable-next-line consistent-return - async ({ searchQueries, isPerfectMatch }, { dispatch, rejectWithValue }) => { - try { - const asyncGetBioEntityFunctions = searchQueries.map(searchQuery => - dispatch(getBioEntity({ searchQuery, isPerfectMatch, addNumbersToEntityNumber: false })), - ); - - const bioEntityContentsActions = (await Promise.all( - asyncGetBioEntityFunctions, - )) as GetMultiBioEntityActions; - - const bioEntityContents = bioEntityContentsActions - .map(bioEntityContentsAction => bioEntityContentsAction?.payload || []) - .flat() - .filter((payload): payload is BioEntityContent => typeof payload !== 'string') - .filter(payload => 'bioEntity' in payload || {}); - - const bioEntityIds = bioEntityContents.map(b => b.bioEntity.elementId); - dispatch(addNumbersToEntityNumberData(bioEntityIds)); - - return bioEntityContents; - } catch (error) { - const errorMessage = getErrorMessage({ - error, - prefix: MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX, - }); - - return rejectWithValue(errorMessage); - } - }, -); +export { getBioEntity, getMultiBioEntity }; diff --git a/src/redux/bioEntity/thunks/getBioEntity.ts b/src/redux/bioEntity/thunks/getBioEntity.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3e9c058bcde397f2efd140c23dfb882c3d7655d --- /dev/null +++ b/src/redux/bioEntity/thunks/getBioEntity.ts @@ -0,0 +1,86 @@ +import { getBioEntitiesIdsFromReaction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction'; +import { SIZE_OF_EMPTY_ARRAY, ZERO } from '@/constants/common'; +import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; +import { apiPath } from '@/redux/apiPath'; +import { selectTab } from '@/redux/drawer/drawer.slice'; +import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; +import { BioEntityContent, BioEntityResponse } from '@/types/models'; +import { PerfectSearchParams } from '@/types/search'; +import { ThunkConfig } from '@/types/store'; +import { getErrorMessage } from '@/utils/getErrorMessage'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { addNumbersToEntityNumberData } from '../../entityNumber/entityNumber.slice'; +import { getReactionsByIds } from '../../reactions/reactions.thunks'; +import { BIO_ENTITY_FETCHING_ERROR_PREFIX } from '../bioEntity.constants'; +import { getMultiBioEntity } from '../bioEntity.thunks'; + +type GetBioEntityProps = PerfectSearchParams; + +export const getBioEntity = createAsyncThunk< + BioEntityContent[] | undefined, + GetBioEntityProps, + ThunkConfig +>( + 'project/getBioEntityContents', + async ( + { searchQuery, isPerfectMatch, addNumbersToEntityNumber = true }, + { rejectWithValue, dispatch }, + ) => { + try { + const response = await axiosInstanceNewAPI.get<BioEntityResponse>( + apiPath.getBioEntityContentsStringWithQuery({ searchQuery, isPerfectMatch }), + ); + + const isDataValidBioEnity = validateDataUsingZodSchema( + response.data, + bioEntityResponseSchema, + ); + + const bioEntityReactionsIds = (response.data?.content || []) + .filter(c => c?.bioEntity?.idReaction) + .map(c => c?.bioEntity?.id) + .filter((id): id is number => typeof id === 'number'); + + if (bioEntityReactionsIds.length > ZERO) { + dispatch( + getReactionsByIds({ + ids: bioEntityReactionsIds, + shouldConcat: true, + }), + ).then(async result => { + if (typeof result.payload === 'string') return; + + const reactions = result.payload?.data; + if (!reactions || reactions.length === SIZE_OF_EMPTY_ARRAY) return; + + const bioEntitiesIds = reactions + .map(reaction => getBioEntitiesIdsFromReaction(reaction)) + .flat(); + + // dispatch(openReactionDrawerById(reactions[FIRST_ARRAY_ELEMENT].id)); + dispatch(selectTab('')); + await dispatch( + getMultiBioEntity({ + searchQueries: bioEntitiesIds, + isPerfectMatch: true, + }), + ); + }); + } + + if (addNumbersToEntityNumber && response.data.content) { + const bioEntityIds = response.data.content.map(b => b.bioEntity.elementId); + dispatch(addNumbersToEntityNumberData(bioEntityIds)); + } + + return isDataValidBioEnity ? response.data.content : undefined; + } catch (error) { + const errorMessage = getErrorMessage({ + error, + prefix: BIO_ENTITY_FETCHING_ERROR_PREFIX, + }); + return rejectWithValue(errorMessage); + } + }, +); diff --git a/src/redux/bioEntity/thunks/getMultiBioEntity.ts b/src/redux/bioEntity/thunks/getMultiBioEntity.ts new file mode 100644 index 0000000000000000000000000000000000000000..23afeb9ceec3a5f13196c6a482bd0e1290bfe40b --- /dev/null +++ b/src/redux/bioEntity/thunks/getMultiBioEntity.ts @@ -0,0 +1,49 @@ +import { BioEntityContent } from '@/types/models'; +import { PerfectMultiSearchParams } from '@/types/search'; +import { ThunkConfig } from '@/types/store'; +import { getErrorMessage } from '@/utils/getErrorMessage'; +import { PayloadAction, createAsyncThunk } from '@reduxjs/toolkit'; +import { addNumbersToEntityNumberData } from '../../entityNumber/entityNumber.slice'; +import { MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX } from '../bioEntity.constants'; +import { getBioEntity } from '../bioEntity.thunks'; + +type GetMultiBioEntityProps = PerfectMultiSearchParams; +type GetMultiBioEntityActions = PayloadAction<BioEntityContent[] | undefined | string>[]; // if error thrown, string containing error message is returned + +export const getMultiBioEntity = createAsyncThunk< + BioEntityContent[], + GetMultiBioEntityProps, + ThunkConfig +>( + 'project/getMultiBioEntity', + // eslint-disable-next-line consistent-return + async ({ searchQueries, isPerfectMatch }, { dispatch, rejectWithValue }) => { + try { + const asyncGetBioEntityFunctions = searchQueries.map(searchQuery => + dispatch(getBioEntity({ searchQuery, isPerfectMatch, addNumbersToEntityNumber: false })), + ); + + const bioEntityContentsActions = (await Promise.all( + asyncGetBioEntityFunctions, + )) as GetMultiBioEntityActions; + + const bioEntityContents = bioEntityContentsActions + .map(bioEntityContentsAction => bioEntityContentsAction?.payload || []) + .flat() + .filter((payload): payload is BioEntityContent => typeof payload !== 'string') + .filter(payload => 'bioEntity' in payload || {}); + + const bioEntityIds = bioEntityContents.map(b => b.bioEntity.elementId); + dispatch(addNumbersToEntityNumberData(bioEntityIds)); + + return bioEntityContents; + } catch (error) { + const errorMessage = getErrorMessage({ + error, + prefix: MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX, + }); + + return rejectWithValue(errorMessage); + } + }, +); diff --git a/src/redux/reactions/reactions.slice.ts b/src/redux/reactions/reactions.slice.ts index 1d0d360c75e768f637a71cddd19338c863ccdf16..f97e9050155d87e496cb8adada0f8e28f0d0f4ba 100644 --- a/src/redux/reactions/reactions.slice.ts +++ b/src/redux/reactions/reactions.slice.ts @@ -1,9 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import { REACTIONS_INITIAL_STATE } from './reactions.constants'; -import { - getReactionsReducer, - resetReactionsDataReducer -} from './reactions.reducers'; +import { getReactionsReducer, resetReactionsDataReducer } from './reactions.reducers'; export const reactionsSlice = createSlice({ name: 'reactions', diff --git a/src/redux/reactions/reactions.thunks.test.ts b/src/redux/reactions/reactions.thunks.test.ts index 88cfb4c4341bbd5234d8b174e59637f2268a715b..d570a341bf204a66eaf062403f6698da0908b654 100644 --- a/src/redux/reactions/reactions.thunks.test.ts +++ b/src/redux/reactions/reactions.thunks.test.ts @@ -28,7 +28,7 @@ describe('reactions thunks', () => { .reply(HttpStatusCode.Ok, reactionsFixture); const { payload } = await store.dispatch(getReactionsByIds(ids)); - expect(payload).toEqual(reactionsFixture); + expect(payload).toEqual({ data: reactionsFixture, shouldConcat: false }); }); it('should return undefined when data response from API is not valid ', async () => { @@ -44,7 +44,7 @@ describe('reactions thunks', () => { mockedAxiosClient.onGet(apiPath.getReactionsWithIds([100])).reply(HttpStatusCode.Ok, []); const { payload } = await store.dispatch(getReactionsByIds([100])); - expect(payload).toEqual([]); + expect(payload).toEqual({ data: [], shouldConcat: false }); }); }); }); diff --git a/src/redux/reactions/reactions.thunks.ts b/src/redux/reactions/reactions.thunks.ts index 93c41f2a1ede0efbfc9ecb9572a1803c957c9b1d..610a83adfced0672fe526d609df7072842e3c264 100644 --- a/src/redux/reactions/reactions.thunks.ts +++ b/src/redux/reactions/reactions.thunks.ts @@ -16,7 +16,7 @@ type GetReactionsByIdsArgs = shouldConcat?: boolean; }; -type GetReactionsByIdsResult = { +export type GetReactionsByIdsResult = { data: Reaction[]; shouldConcat: boolean; };