From 5604869238a9f6762719c14eeea17df1814ad1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com> Date: Mon, 18 Mar 2024 21:44:29 +0100 Subject: [PATCH] feat: add loading of reactions pins --- .../getBioEntitiesIdsFromReaction.ts | 9 ++ .../handleReactionResults.test.ts | 12 +- .../mapSingleClick/handleReactionResults.ts | 16 +-- src/redux/bioEntity/bioEntity.thunks.ts | 108 +----------------- src/redux/bioEntity/thunks/getBioEntity.ts | 86 ++++++++++++++ .../bioEntity/thunks/getMultiBioEntity.ts | 49 ++++++++ src/redux/reactions/reactions.slice.ts | 5 +- src/redux/reactions/reactions.thunks.test.ts | 4 +- src/redux/reactions/reactions.thunks.ts | 2 +- 9 files changed, 166 insertions(+), 125 deletions(-) create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction.ts create mode 100644 src/redux/bioEntity/thunks/getBioEntity.ts create mode 100644 src/redux/bioEntity/thunks/getMultiBioEntity.ts 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 00000000..d6c2ae75 --- /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 58ee8bf0..61e8c191 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 564f106b..bfbcaaa3 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 78373523..18fa812c 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 00000000..a3e9c058 --- /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 00000000..23afeb9c --- /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 1d0d360c..f97e9050 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 88cfb4c4..d570a341 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 93c41f2a..610a83ad 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; }; -- GitLab