From a9bc74d071aa7e6f72151bdbf5a08aecc103fa77 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Tue, 31 Dec 2024 14:32:47 +0100 Subject: [PATCH 1/2] use new publication endpoint --- .../PublicationsModal/PublicationsModal.tsx | 12 +- ...PublicationsModalLayout.component.test.tsx | 20 +- .../utils/getBasePublications.test.ts | 16 +- .../utils/getBasePublications.ts | 16 +- .../utils/getStandarizedPublications.ts | 8 +- .../mapBasePublicationToStandarized.test.ts | 14 +- .../utils/mapBasePublicationToStandarized.ts | 7 +- .../utils/useDownloadPublicationsAsCSVFile.ts | 4 +- .../FilterBySubmapHeader.test.tsx | 4 +- src/models/mocks/publicationsResponseMock.ts | 351 +++++++++--------- src/models/pageableSchema.ts | 2 +- src/models/publicationsResponseSchema.ts | 10 - src/models/publicationsSchema.ts | 8 +- .../publications/publications.selectors.ts | 11 +- src/redux/publications/publications.thunks.ts | 18 +- src/redux/publications/publications.types.ts | 4 +- src/types/models.ts | 6 +- 17 files changed, 248 insertions(+), 263 deletions(-) delete mode 100644 src/models/publicationsResponseSchema.ts diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx index 69cbc09b..e37d378e 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx @@ -19,13 +19,13 @@ export const PublicationsModal = (): JSX.Element => { const parsedData: PublicationsTableData[] | undefined = useMemo(() => { const dd = data?.map(item => ({ - pubmedId: item.publication.article.pubmedId, - title: item.publication.article.title, - authors: item.publication.article.authors, - journal: item.publication.article.journal, - year: item.publication.article.year, + pubmedId: item.article.pubmedId, + title: item.article.title, + authors: item.article.authors, + journal: item.article.journal, + year: item.article.year, elementsOnMap: JSON.stringify(item.elements) || EMPTY_ARRAY_STRING, // table data accepts only string | string[] - submaps: mapsNames[item.elements[FIRST_ARRAY_ELEMENT].modelId], + submaps: mapsNames[item.elements[FIRST_ARRAY_ELEMENT].model], })); return dd || []; }, [data, mapsNames]); diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.test.tsx index aa7755b5..95bf0cd7 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.test.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.test.tsx @@ -8,7 +8,7 @@ import { apiPath } from '@/redux/apiPath'; import { downloadFileFromBlob } from '@/redux/export/export.utils'; import { StoreType } from '@/redux/store'; import { BioEntityContent, Publication } from '@/types/models'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { InitialStoreState, getReduxWrapperWithStore, @@ -28,7 +28,7 @@ const THIRD_ELEMENT_ID = 300; const FOURTH_ELEMENT_ID = 400; const BASE_PUBLICATION: Publication = - PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.data[FIRST_ARRAY_ELEMENT]; + PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.content[FIRST_ARRAY_ELEMENT]; const BASE_ELEMENT = BASE_PUBLICATION.elements[FIRST_ARRAY_ELEMENT]; const PUBLICATION: Publication = { ...BASE_PUBLICATION, @@ -36,22 +36,22 @@ const PUBLICATION: Publication = { { ...BASE_ELEMENT, id: FIRST_ELEMENT_ID, - modelId: FIRST_MODEL_ID, + model: FIRST_MODEL_ID, }, { ...BASE_ELEMENT, id: SECOND_ELEMENT_ID, - modelId: SECOND_MODEL_ID, + model: SECOND_MODEL_ID, }, { ...BASE_ELEMENT, id: THIRD_ELEMENT_ID, - modelId: THIRD_MODEL_ID, // model id duplicate + model: THIRD_MODEL_ID, // model id duplicate }, { ...BASE_ELEMENT, id: FOURTH_ELEMENT_ID, - modelId: THIRD_MODEL_ID, // model id duplicate + model: THIRD_MODEL_ID, // model id duplicate }, ], }; @@ -77,7 +77,7 @@ jest.mock('../utils/fetchElementData'); })[id] as BioEntityContent, ); -const mockedAxiosClient = mockNetworkResponse(); +const mockedAxiosNewClient = mockNetworkNewAPIResponse(); const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => { const { Wrapper, store } = getReduxWrapperWithStore(initialStore); @@ -96,11 +96,11 @@ const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } describe('PublicationsModalLayout - component', () => { const length = 1; - mockedAxiosClient + mockedAxiosNewClient .onGet(apiPath.getPublications({ params: { length } })) .reply(HttpStatusCode.Ok, { ...PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK, - data: [PUBLICATION], + content: [PUBLICATION], }); it('should render download csv button', () => { @@ -114,7 +114,7 @@ describe('PublicationsModalLayout - component', () => { publications: { data: { ...PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK, - data: [PUBLICATION], + content: [PUBLICATION], filteredSize: length, }, loading: 'succeeded', diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.test.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.test.ts index ffa8191a..ca246ead 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.test.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.test.ts @@ -1,11 +1,11 @@ import { apiPath } from '@/redux/apiPath'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { HttpStatusCode } from 'axios'; import { showToast } from '@/utils/showToast'; import { PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK } from '../../../../../../models/mocks/publicationsResponseMock'; import { getBasePublications } from './getBasePublications'; -const mockedAxiosClient = mockNetworkResponse(); +const mockedAxiosNewClient = mockNetworkNewAPIResponse(); jest.mock('./../../../../../../utils/showToast'); @@ -13,17 +13,19 @@ describe('getBasePublications - util', () => { const length = 10; it('should return valid data if provided', async () => { - mockedAxiosClient + mockedAxiosNewClient .onGet(apiPath.getPublications({ params: { length } })) - .reply(HttpStatusCode.Ok, PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK); + .reply(HttpStatusCode.Ok, JSON.stringify(PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK)); const result = await getBasePublications({ length }); - expect(result).toStrictEqual(PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.data); + expect(result).toStrictEqual( + JSON.parse(JSON.stringify(PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.content)), + ); }); it('should return empty array if data structure is invalid', async () => { - mockedAxiosClient + mockedAxiosNewClient .onGet(apiPath.getPublications({ params: { length } })) .reply(HttpStatusCode.Ok, { randomObject: true }); @@ -33,7 +35,7 @@ describe('getBasePublications - util', () => { }); it('should return empty array and show toast error if http error', async () => { - mockedAxiosClient + mockedAxiosNewClient .onGet(apiPath.getPublications({ params: { length } })) .reply(HttpStatusCode.BadRequest); diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts index d149d031..e58c2f9f 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts @@ -1,11 +1,12 @@ -import { publicationsResponseSchema } from '@/models/publicationsResponseSchema'; import { apiPath } from '@/redux/apiPath'; import { PUBLICATIONS_FETCHING_ERROR_PREFIX } from '@/redux/publications/publications.constatns'; -import { axiosInstance } from '@/services/api/utils/axiosInstance'; -import { Publication, PublicationsResponse } from '@/types/models'; +import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; +import { FilteredPageOf, Publication } from '@/types/models'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; import { getErrorMessage } from '@/utils/getErrorMessage'; import { showToast } from '@/utils/showToast'; +import { pageableSchema } from '@/models/pageableSchema'; +import { publicationSchema } from '@/models/publicationsSchema'; interface Args { length: number; @@ -13,13 +14,16 @@ interface Args { export const getBasePublications = async ({ length }: Args): Promise<Publication[]> => { try { - const response = await axiosInstance.get<PublicationsResponse>( + const response = await axiosInstanceNewAPI.get<FilteredPageOf<Publication>>( apiPath.getPublications({ params: { length } }), ); - const isDataValid = validateDataUsingZodSchema(response.data, publicationsResponseSchema); + const isDataValid = validateDataUsingZodSchema( + response.data, + pageableSchema(publicationSchema), + ); - return isDataValid ? response.data.data : []; + return isDataValid ? response.data.content : []; } catch (error) { const errorMessage = getErrorMessage({ error, prefix: PUBLICATIONS_FETCHING_ERROR_PREFIX }); showToast({ diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts index c59d2e00..894f194b 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts @@ -14,17 +14,17 @@ export const getStandarizedPublications = async ({ publications, modelNameIdMap, }: Args): Promise<StandarizedPublication[]> => { - const getStandarizedPublicationsFuncs = publications.map( + const getStandardizedPublicationsFuncs = publications.map( publication => () => mapBasePublicationToStandarized(publication, { modelNameIdMap, }), ); - const standarizedPublications = await runInSequence<StandarizedPublication>( - getStandarizedPublicationsFuncs, + const standardizedPublications = await runInSequence<StandarizedPublication>( + getStandardizedPublicationsFuncs, SEQUENCE_CHUNK_SIZE, ); - return standarizedPublications; + return standardizedPublications; }; diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.test.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.test.ts index 3a556eff..05cbe84f 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.test.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.test.ts @@ -23,7 +23,7 @@ const MODEL_NAME_ID_MAP: Record<number, string> = { }; const BASE_PUBLICATION: Publication = - PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.data[FIRST_ARRAY_ELEMENT]; + PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK.content[FIRST_ARRAY_ELEMENT]; const BASE_ELEMENT = BASE_PUBLICATION.elements[FIRST_ARRAY_ELEMENT]; const PUBLICATION: Publication = { ...BASE_PUBLICATION, @@ -31,22 +31,22 @@ const PUBLICATION: Publication = { { ...BASE_ELEMENT, id: FIRST_ELEMENT_ID, - modelId: FIRST_MODEL_ID, + model: FIRST_MODEL_ID, }, { ...BASE_ELEMENT, id: SECOND_ELEMENT_ID, - modelId: SECOND_MODEL_ID, + model: SECOND_MODEL_ID, }, { ...BASE_ELEMENT, id: THIRD_ELEMENT_ID, - modelId: THIRD_MODEL_ID, // model id duplicate + model: THIRD_MODEL_ID, // model id duplicate }, { ...BASE_ELEMENT, id: FOURTH_ELEMENT_ID, - modelId: THIRD_MODEL_ID, // model id duplicate + model: THIRD_MODEL_ID, // model id duplicate }, ], }; @@ -80,7 +80,7 @@ const getFuncResult = async ( describe('mapBasePublicationToStandarized - util', () => { it('should return valid pubmedId, journal, year and title', async () => { const results = await getFuncResult(PUBLICATION); - const { pubmedId, journal, year, title } = PUBLICATION.publication.article; + const { pubmedId, journal, year, title } = PUBLICATION.article; expect(results.pubmedId).toBe(pubmedId); expect(results.journal).toBe(journal); @@ -90,7 +90,7 @@ describe('mapBasePublicationToStandarized - util', () => { it('should return joined authors', async () => { const results = await getFuncResult(PUBLICATION); - const { authors } = PUBLICATION.publication.article; + const { authors } = PUBLICATION.article; expect(results.authors).toBe(authors.join(',')); }); diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.ts index 84b7d269..dea109c1 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/mapBasePublicationToStandarized.ts @@ -13,13 +13,10 @@ export const mapBasePublicationToStandarized = async ( publication: Publication, options: Options, ): Promise<StandarizedPublication> => { - const { - publication: { article }, - elements, - } = publication; + const { article, elements } = publication; const { modelNameIdMap } = options; const { title, authors, journal, year, pubmedId } = article; - const modelNames = elements.map(({ modelId }) => modelNameIdMap[modelId]); + const modelNames = elements.map(({ model }) => modelNameIdMap[model]); const elementsData = await Promise.all(elements.map(async ({ id }) => fetchElementData(`${id}`))); const elementsIds = elementsData .filter((element): element is BioEntityContent => element !== undefined) diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts index 531faf9c..cdd58d24 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts @@ -41,12 +41,12 @@ export const useDownloadPublicationsAsCSVFile = (): UseDownloadPublicationsAsCSV ? searchedPublicationsList : await getAllBasePublications(); - const standarizedPublications = await getStandarizedPublications({ + const standardizedPublications = await getStandarizedPublications({ publications, modelNameIdMap, }); - return standarizedPublications; + return standardizedPublications; }; const downloadPublicationsAsCSVFile = async (): Promise<void> => { diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/FilterBySubmapHeader/FilterBySubmapHeader.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/FilterBySubmapHeader/FilterBySubmapHeader.test.tsx index f238cef3..237c4c98 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/FilterBySubmapHeader/FilterBySubmapHeader.test.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/FilterBySubmapHeader/FilterBySubmapHeader.test.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { act } from 'react-dom/test-utils'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { InitialStoreState, getReduxWrapperWithStore, @@ -16,7 +16,7 @@ import { MODELS_MOCK } from '@/models/mocks/modelsMock'; import { FIRST_ARRAY_ELEMENT, ZERO } from '@/constants/common'; import { FilterBySubmapHeader } from './FilterBySubmapHeader.component'; -mockNetworkResponse(); +mockNetworkNewAPIResponse(); const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); diff --git a/src/models/mocks/publicationsResponseMock.ts b/src/models/mocks/publicationsResponseMock.ts index c165eb98..01240a65 100644 --- a/src/models/mocks/publicationsResponseMock.ts +++ b/src/models/mocks/publicationsResponseMock.ts @@ -1,283 +1,266 @@ -import { PublicationsResponse } from '@/types/models'; +import { FilteredPageOf, Publication } from '@/types/models'; +import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; -export const PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK: PublicationsResponse = { - data: [ +export const PUBLICATIONS_DEFAULT_SEARCH_FIRST_10_MOCK: FilteredPageOf<Publication> = { + content: [ { elements: [ { + ...bioEntityFixture, id: 19519, - modelId: 52, - type: 'REACTION', + model: 52, }, ], - publication: { - article: { - title: 'The glutamate receptor ion channels.', - authors: ['Dingledine R', ' Borges K', ' Bowie D', ' Traynelis SF.'], - journal: 'Pharmacological reviews', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10049997', - pubmedId: '10049997', - citationCount: 2458, - }, + article: { + title: 'The glutamate receptor ion channels.', + authors: ['Dingledine R', ' Borges K', ' Bowie D', ' Traynelis SF.'], + journal: 'Pharmacological reviews', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10049997', + pubmedId: '10049997', + citationCount: 2458, }, }, { elements: [ { + ...bioEntityFixture, id: 16167, - modelId: 61, - type: 'REACTION', + model: 61, }, ], - publication: { - article: { - title: 'Regulation of JNK signaling by GSTp.', - authors: [ - 'Adler V', - ' Yin Z', - ' Fuchs SY', - ' Benezra M', - ' Rosario L', - ' Tew KD', - ' Pincus MR', - ' Sardana M', - ' Henderson CJ', - ' Wolf CR', - ' Davis RJ', - ' Ronai Z.', - ], - journal: 'The EMBO journal', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10064598', - pubmedId: '10064598', - citationCount: 656, - }, + article: { + title: 'Regulation of JNK signaling by GSTp.', + authors: [ + 'Adler V', + ' Yin Z', + ' Fuchs SY', + ' Benezra M', + ' Rosario L', + ' Tew KD', + ' Pincus MR', + ' Sardana M', + ' Henderson CJ', + ' Wolf CR', + ' Davis RJ', + ' Ronai Z.', + ], + journal: 'The EMBO journal', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10064598', + pubmedId: '10064598', + citationCount: 656, }, }, { elements: [ { + ...bioEntityFixture, id: 17823, - modelId: 52, - type: 'REACTION', + model: 52, }, { + ...bioEntityFixture, id: 19461, - modelId: 52, - type: 'REACTION', + model: 52, }, ], - publication: { - article: { - title: - 'Generic signals and specific outcomes: signaling through Ca2+, calcineurin, and NF-AT.', - authors: ['Crabtree GR.'], - journal: 'Cell', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10089876', - pubmedId: '10089876', - citationCount: 454, - }, + article: { + title: + 'Generic signals and specific outcomes: signaling through Ca2+, calcineurin, and NF-AT.', + authors: ['Crabtree GR.'], + journal: 'Cell', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10089876', + pubmedId: '10089876', + citationCount: 454, }, }, { elements: [ { + ...bioEntityFixture, id: 18189, - modelId: 52, - type: 'REACTION', + model: 52, }, { + ...bioEntityFixture, id: 18729, - modelId: 52, - type: 'REACTION', + model: 52, }, ], - publication: { - article: { - title: 'G protein regulation of adenylate cyclase.', - authors: ['Simonds WF.'], - journal: 'Trends in pharmacological sciences', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10101967', - pubmedId: '10101967', - citationCount: 139, - }, + article: { + title: 'G protein regulation of adenylate cyclase.', + authors: ['Simonds WF.'], + journal: 'Trends in pharmacological sciences', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10101967', + pubmedId: '10101967', + citationCount: 139, }, }, { elements: [ { + ...bioEntityFixture, id: 16077, - modelId: 58, - type: 'REACTION', + model: 58, }, { + ...bioEntityFixture, id: 16135, - modelId: 58, - type: 'REACTION', + model: 58, }, ], - publication: { - article: { - title: - 'Akt promotes cell survival by phosphorylating and inhibiting a Forkhead transcription factor.', - authors: [ - 'Brunet A', - ' Bonni A', - ' Zigmond MJ', - ' Lin MZ', - ' Juo P', - ' Hu LS', - ' Anderson MJ', - ' Arden KC', - ' Blenis J', - ' Greenberg ME.', - ], - journal: 'Cell', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10102273', - pubmedId: '10102273', - citationCount: 4019, - }, + article: { + title: + 'Akt promotes cell survival by phosphorylating and inhibiting a Forkhead transcription factor.', + authors: [ + 'Brunet A', + ' Bonni A', + ' Zigmond MJ', + ' Lin MZ', + ' Juo P', + ' Hu LS', + ' Anderson MJ', + ' Arden KC', + ' Blenis J', + ' Greenberg ME.', + ], + journal: 'Cell', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10102273', + pubmedId: '10102273', + citationCount: 4019, }, }, { elements: [ { + ...bioEntityFixture, id: 15955, - modelId: 55, - type: 'REACTION', + model: 55, }, ], - publication: { - article: { - title: 'Ca2+-induced apoptosis through calcineurin dephosphorylation of BAD.', - authors: [ - 'Wang HG', - ' Pathan N', - ' Ethell IM', - ' Krajewski S', - ' Yamaguchi Y', - ' Shibasaki F', - ' McKeon F', - ' Bobo T', - ' Franke TF', - ' Reed JC.', - ], - journal: 'Science (New York, N.Y.)', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10195903', - pubmedId: '10195903', - citationCount: 708, - }, + article: { + title: 'Ca2+-induced apoptosis through calcineurin dephosphorylation of BAD.', + authors: [ + 'Wang HG', + ' Pathan N', + ' Ethell IM', + ' Krajewski S', + ' Yamaguchi Y', + ' Shibasaki F', + ' McKeon F', + ' Bobo T', + ' Franke TF', + ' Reed JC.', + ], + journal: 'Science (New York, N.Y.)', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10195903', + pubmedId: '10195903', + citationCount: 708, }, }, { elements: [ { + ...bioEntityFixture, id: 15937, - modelId: 55, - type: 'REACTION', + model: 55, }, { + ...bioEntityFixture, id: 15955, - modelId: 55, - type: 'REACTION', + model: 55, }, ], - publication: { - article: { - title: - 'The proapoptotic activity of the Bcl-2 family member Bim is regulated by interaction with the dynein motor complex.', - authors: ['Puthalakath H', ' Huang DC', " O'Reilly LA", ' King SM', ' Strasser A.'], - journal: 'Molecular cell', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10198631', - pubmedId: '10198631', - citationCount: 662, - }, + article: { + title: + 'The proapoptotic activity of the Bcl-2 family member Bim is regulated by interaction with the dynein motor complex.', + authors: ['Puthalakath H', ' Huang DC', " O'Reilly LA", ' King SM', ' Strasser A.'], + journal: 'Molecular cell', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10198631', + pubmedId: '10198631', + citationCount: 662, }, }, { elements: [ { + ...bioEntityFixture, id: 15948, - modelId: 55, - type: 'REACTION', + model: 55, }, ], - publication: { - article: { - title: - 'An APAF-1.cytochrome c multimeric complex is a functional apoptosome that activates procaspase-9.', - authors: ['Zou H', ' Li Y', ' Liu X', ' Wang X.'], - journal: 'The Journal of biological chemistry', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10206961', - pubmedId: '10206961', - citationCount: 1162, - }, + article: { + title: + 'An APAF-1.cytochrome c multimeric complex is a functional apoptosome that activates procaspase-9.', + authors: ['Zou H', ' Li Y', ' Liu X', ' Wang X.'], + journal: 'The Journal of biological chemistry', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10206961', + pubmedId: '10206961', + citationCount: 1162, }, }, { elements: [ { + ...bioEntityFixture, id: 16286, - modelId: 62, - type: 'REACTION', + model: 62, }, ], - publication: { - article: { - title: - 'Biochemical characterization and crystal structure determination of human heart short chain L-3-hydroxyacyl-CoA dehydrogenase provide insights into catalytic mechanism.', - authors: [ - 'Barycki JJ', - " O'Brien LK", - ' Bratt JM', - ' Zhang R', - ' Sanishvili R', - ' Strauss AW', - ' Banaszak LJ.', - ], - journal: 'Biochemistry', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10231530', - pubmedId: '10231530', - citationCount: 56, - }, + article: { + title: + 'Biochemical characterization and crystal structure determination of human heart short chain L-3-hydroxyacyl-CoA dehydrogenase provide insights into catalytic mechanism.', + authors: [ + 'Barycki JJ', + " O'Brien LK", + ' Bratt JM', + ' Zhang R', + ' Sanishvili R', + ' Strauss AW', + ' Banaszak LJ.', + ], + journal: 'Biochemistry', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10231530', + pubmedId: '10231530', + citationCount: 56, }, }, { elements: [ { + ...bioEntityFixture, id: 17780, - modelId: 52, - type: 'REACTION', + model: 52, }, { + ...bioEntityFixture, id: 17937, - modelId: 52, - type: 'REACTION', + model: 52, }, ], - publication: { - article: { - title: 'The Ca-calmodulin-dependent protein kinase cascade.', - authors: ['Soderling TR.'], - journal: 'Trends in biochemical sciences', - year: 1999, - link: 'https://www.ncbi.nlm.nih.gov/pubmed/10366852', - pubmedId: '10366852', - citationCount: 322, - }, + article: { + title: 'The Ca-calmodulin-dependent protein kinase cascade.', + authors: ['Soderling TR.'], + journal: 'Trends in biochemical sciences', + year: 1999, + link: 'https://www.ncbi.nlm.nih.gov/pubmed/10366852', + pubmedId: '10366852', + citationCount: 322, }, }, ], - totalSize: 159, + totalElements: 159, filteredSize: 1586, - length: 10, - page: 0, + size: 10, + number: 0, + numberOfElements: 10, + totalPages: 16, }; diff --git a/src/models/pageableSchema.ts b/src/models/pageableSchema.ts index 9ec3a540..4e772ffc 100644 --- a/src/models/pageableSchema.ts +++ b/src/models/pageableSchema.ts @@ -7,7 +7,7 @@ export const pageableSchema = <T extends ZodTypeAny>(type: T) => totalPages: z.number().nonnegative(), totalElements: z.number().nonnegative(), numberOfElements: z.number().nonnegative(), - size: z.number().positive(), + size: z.number().nonnegative(), number: z.number().nonnegative(), content: z.array(type), }); diff --git a/src/models/publicationsResponseSchema.ts b/src/models/publicationsResponseSchema.ts deleted file mode 100644 index 5bdcca5b..00000000 --- a/src/models/publicationsResponseSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { z } from 'zod'; -import { publicationSchema } from './publicationsSchema'; - -export const publicationsResponseSchema = z.object({ - data: z.array(publicationSchema), - totalSize: z.number(), - filteredSize: z.number(), - length: z.number(), - page: z.number(), -}); diff --git a/src/models/publicationsSchema.ts b/src/models/publicationsSchema.ts index b1574088..14ac77ff 100644 --- a/src/models/publicationsSchema.ts +++ b/src/models/publicationsSchema.ts @@ -1,10 +1,8 @@ import { z } from 'zod'; -import { targetElementSchema } from './targetElementSchema'; +import { bioEntitySchema } from '@/models/bioEntitySchema'; import { articleSchema } from './articleSchema'; export const publicationSchema = z.object({ - elements: z.array(targetElementSchema), - publication: z.object({ - article: articleSchema, - }), + elements: z.array(bioEntitySchema), + article: articleSchema, }); diff --git a/src/redux/publications/publications.selectors.ts b/src/redux/publications/publications.selectors.ts index 8ed16fac..202156a9 100644 --- a/src/redux/publications/publications.selectors.ts +++ b/src/redux/publications/publications.selectors.ts @@ -11,20 +11,23 @@ export const publicationsDataSelector = createSelector( export const publicationsListDataSelector = createSelector( publicationsDataSelector, - data => data?.data, + data => data?.content, ); /** totalSize is number of pages */ -export const totalSizeSelector = createSelector(publicationsDataSelector, data => data?.totalSize); +export const totalSizeSelector = createSelector( + publicationsDataSelector, + data => data?.totalElements, +); export const filteredSizeSelector = createSelector( publicationsDataSelector, data => data?.filteredSize, ); -export const currentPageSelector = createSelector(publicationsDataSelector, data => data?.page); +export const currentPageSelector = createSelector(publicationsDataSelector, data => data?.number); export const paginationSelector = createSelector(publicationsDataSelector, data => ({ - pageIndex: data?.page || ZERO, + pageIndex: data?.number || ZERO, pageSize: 10, })); diff --git a/src/redux/publications/publications.thunks.ts b/src/redux/publications/publications.thunks.ts index 4c03def9..c173c620 100644 --- a/src/redux/publications/publications.thunks.ts +++ b/src/redux/publications/publications.thunks.ts @@ -1,23 +1,29 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { axiosInstance } from '@/services/api/utils/axiosInstance'; -import { PublicationsResponse } from '@/types/models'; -import { publicationsResponseSchema } from '@/models/publicationsResponseSchema'; +import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; import { ThunkConfig } from '@/types/store'; import { getError } from '@/utils/error-report/getError'; +import { FilteredPageOf, Publication } from '@/types/models'; +import { pageableSchema } from '@/models/pageableSchema'; +import { publicationSchema } from '@/models/publicationsSchema'; import { GetPublicationsParams } from './publications.types'; import { apiPath } from '../apiPath'; import { PUBLICATIONS_FETCHING_ERROR_PREFIX } from './publications.constatns'; export const getPublications = createAsyncThunk< - PublicationsResponse | undefined, + FilteredPageOf<Publication> | undefined, GetPublicationsParams, ThunkConfig >('publications/getPublications', async params => { try { - const response = await axiosInstance.get<PublicationsResponse>(apiPath.getPublications(params)); + const response = await axiosInstanceNewAPI.get<FilteredPageOf<Publication>>( + apiPath.getPublications(params), + ); - const isDataValid = validateDataUsingZodSchema(response.data, publicationsResponseSchema); + const isDataValid = validateDataUsingZodSchema( + response.data, + pageableSchema(publicationSchema), + ); return isDataValid ? response.data : undefined; } catch (error) { diff --git a/src/redux/publications/publications.types.ts b/src/redux/publications/publications.types.ts index 2654b800..792e7544 100644 --- a/src/redux/publications/publications.types.ts +++ b/src/redux/publications/publications.types.ts @@ -1,10 +1,10 @@ import { FetchDataState } from '@/types/fetchDataState'; -import { PublicationsResponse } from '@/types/models'; +import { FilteredPageOf, Publication } from '@/types/models'; export type SortColumn = '' | 'pubmedId' | 'title' | 'authors' | 'journal' | 'year' | 'level'; export type SortOrder = 'asc' | 'desc'; -export type PublicationsState = FetchDataState<PublicationsResponse> & { +export type PublicationsState = FetchDataState<FilteredPageOf<Publication>> & { sortColumn: SortColumn; sortOrder: SortOrder; selectedModelId?: string; diff --git a/src/types/models.ts b/src/types/models.ts index af20167b..b01270f9 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -49,7 +49,6 @@ import { import { overviewImageView } from '@/models/overviewImageView'; import { pluginSchema } from '@/models/pluginSchema'; import { projectSchema } from '@/models/projectSchema'; -import { publicationsResponseSchema } from '@/models/publicationsResponseSchema'; import { publicationSchema } from '@/models/publicationsSchema'; import { referenceSchema } from '@/models/referenceSchema'; import { sessionSchemaValid } from '@/models/sessionValidSchema'; @@ -150,7 +149,6 @@ export type Color = z.infer<typeof colorSchema>; export type Statistics = z.infer<typeof statisticsSchema>; export type CompartmentPathway = z.infer<typeof compartmentPathwaySchema>; export type CompartmentPathwayDetails = z.infer<typeof compartmentPathwayDetailsSchema>; -export type PublicationsResponse = z.infer<typeof publicationsResponseSchema>; export type Publication = z.infer<typeof publicationSchema>; export type ExportNetwork = z.infer<typeof exportNetworkchema>; export type ExportElements = z.infer<typeof exportElementsSchema>; @@ -180,4 +178,8 @@ export type PageOf<T> = { content: T[]; }; +export type FilteredPageOf<T> = PageOf<T> & { + filteredSize: number; +}; + export type OAuth = z.infer<typeof oauthSchema>; -- GitLab From 66ceeb98dd1cf29a51fe18d0cb123874703fd7d7 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Thu, 2 Jan 2025 10:12:46 +0100 Subject: [PATCH 2/2] use new api for fetching publication list --- .../ElementLink.component.test.tsx | 96 +++++++++---------- .../ElementLink/ElementLink.component.tsx | 57 ++++------- .../ElementsOnMapCell.component.test.tsx | 72 +++----------- .../ElementsOnMapCell.component.tsx | 4 +- .../PublicationsTable.component.tsx | 11 ++- .../mapSingleClick/handleAliasResults.test.ts | 3 +- src/redux/apiPath.ts | 2 +- .../publications/publications.selectors.ts | 3 + src/redux/reactions/isReactionBioentity.ts | 5 + 9 files changed, 98 insertions(+), 155 deletions(-) create mode 100644 src/redux/reactions/isReactionBioentity.ts diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx index 97d2050f..cdd2e42f 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx @@ -5,7 +5,7 @@ import { apiPath } from '@/redux/apiPath'; import { DEFAULT_POSITION } from '@/redux/map/map.constants'; import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures'; import { AppDispatch, RootState } from '@/redux/store'; -import { TargetElement } from '@/types/models'; +import { BioEntity, MapModel } from '@/types/models'; import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { InitialStoreState, @@ -15,20 +15,44 @@ import { render, screen, waitFor } from '@testing-library/react'; import { HttpStatusCode } from 'axios'; import { MockStoreEnhanced } from 'redux-mock-store'; import MapBackgroundsEnum from '@/redux/map/map.enums'; +import { isReactionBioEntity } from '@/redux/reactions/isReactionBioentity'; import { ElementLink } from './ElementLink.component'; const mockedAxiosNewClient = mockNetworkNewAPIResponse(); -const TARGET_ELEMENT: TargetElement = { +const TARGET_ELEMENT: BioEntity = { + ...bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT].bioEntity, id: 123, - modelId: 52, - type: 'REACTION', + model: 52, + idReaction: 're1234', +}; + +const MODEL: MapModel = { + ...modelsFixture[FIRST_ARRAY_ELEMENT], + id: TARGET_ELEMENT.model, +}; + +const OTHER_MODEL: MapModel = { + ...modelsFixture[FIRST_ARRAY_ELEMENT], }; interface Props { - target: TargetElement; + target: BioEntity; } +const getElementText = (bioEntity: BioEntity): string => { + const isReaction = isReactionBioEntity(bioEntity); + const prefix = isReaction ? 'Reaction: ' : 'Element: '; + + return prefix + bioEntity.elementId; +}; + +const getSearchQuery = (bioEntity: BioEntity): string => { + const isReaction = isReactionBioEntity(bioEntity); + + return (isReaction ? 'reaction:' : 'element:') + bioEntity.id; +}; + const renderComponent = ( props: Props, initialStoreState: InitialStoreState = {}, @@ -48,18 +72,6 @@ const renderComponent = ( }; describe('ElementLink - component', () => { - describe('when initialized', () => { - beforeEach(() => { - renderComponent({ target: TARGET_ELEMENT }, INITIAL_STORE_STATE_MOCK); - }); - - it('should show loading indicator', () => { - const loadingIndicator = screen.getByAltText('spinner icon'); - - expect(loadingIndicator).toBeInTheDocument(); - }); - }); - describe('when loaded', () => { mockedAxiosNewClient .onGet( @@ -74,19 +86,11 @@ describe('ElementLink - component', () => { renderComponent({ target: TARGET_ELEMENT }, INITIAL_STORE_STATE_MOCK); }); - it('should not show loading indicator', async () => { - const loadingIndicator = screen.getByAltText('spinner icon'); - - await waitFor(() => { - expect(loadingIndicator).not.toBeInTheDocument(); - }); - }); - it('should should show element id', async () => { - const { elementId } = bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT].bioEntity; + const bioEntity = TARGET_ELEMENT; await waitFor(() => { - expect(screen.getByText(elementId)).toBeInTheDocument(); + expect(screen.getByText(getElementText(bioEntity))).toBeInTheDocument(); }); }); }); @@ -108,20 +112,15 @@ describe('ElementLink - component', () => { ...INITIAL_STORE_STATE_MOCK, models: { ...INITIAL_STORE_STATE_MOCK.models, - data: [ - { - ...modelsFixture[FIRST_ARRAY_ELEMENT], - id: TARGET_ELEMENT.modelId, - }, - ], + data: [MODEL], }, }, ); - const { elementId } = bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT].bioEntity; + const bioEntity = TARGET_ELEMENT; await waitFor(() => { - const link = screen.getByText(elementId); + const link = screen.getByText(getElementText(bioEntity)); link.click(); const actions = store.getActions(); @@ -150,7 +149,7 @@ describe('ElementLink - component', () => { expect(actions).toEqual( expect.arrayContaining([ expect.objectContaining({ - payload: elementId, + payload: getSearchQuery(bioEntity), type: 'drawer/openSearchDrawerWithSelectedTab', }), ]), @@ -161,8 +160,8 @@ describe('ElementLink - component', () => { expect.arrayContaining([ expect.objectContaining({ payload: { - modelId: TARGET_ELEMENT.modelId, - modelName: modelsFixture[FIRST_ARRAY_ELEMENT].name, + modelId: TARGET_ELEMENT.model, + modelName: MODEL.name, }, type: 'map/openMapAndSetActive', }), @@ -189,23 +188,18 @@ describe('ElementLink - component', () => { ...INITIAL_STORE_STATE_MOCK, models: { ...INITIAL_STORE_STATE_MOCK.models, - data: [ - { - ...modelsFixture[FIRST_ARRAY_ELEMENT], - id: TARGET_ELEMENT.modelId, - }, - ], + data: [MODEL, OTHER_MODEL], }, map: { ...INITIAL_STORE_STATE_MOCK.map, data: { ...INITIAL_STORE_STATE_MOCK.map.data, - modelId: modelsFixture[FIRST_ARRAY_ELEMENT].id, + modelId: OTHER_MODEL.id, }, openedMaps: [ { - modelId: TARGET_ELEMENT.modelId, - modelName: modelsFixture[FIRST_ARRAY_ELEMENT].name, + modelId: TARGET_ELEMENT.model, + modelName: MODEL.name, lastPosition: DEFAULT_POSITION, }, ], @@ -214,10 +208,10 @@ describe('ElementLink - component', () => { }, ); - const { elementId } = bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT].bioEntity; + const bioEntity = TARGET_ELEMENT; await waitFor(() => { - const link = screen.getByText(elementId); + const link = screen.getByText(getElementText(bioEntity)); link.click(); const actions = store.getActions(); @@ -246,7 +240,7 @@ describe('ElementLink - component', () => { expect(actions).toEqual( expect.arrayContaining([ expect.objectContaining({ - payload: elementId, + payload: getSearchQuery(bioEntity), type: 'drawer/openSearchDrawerWithSelectedTab', }), ]), @@ -257,7 +251,7 @@ describe('ElementLink - component', () => { expect.arrayContaining([ expect.objectContaining({ payload: { - modelId: TARGET_ELEMENT.modelId, + modelId: TARGET_ELEMENT.model, }, type: 'map/setActiveMap', }), diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.tsx index a3f28f15..8bf24727 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.tsx @@ -13,55 +13,46 @@ import { closeModal } from '@/redux/modal/modal.slice'; import { modelsNameMapSelector } from '@/redux/models/models.selectors'; import { getSearchData } from '@/redux/search/search.thunks'; import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; -import { LoadingIndicator } from '@/shared/LoadingIndicator'; -import { BioEntityContent, TargetElement } from '@/types/models'; -import { useEffect, useState } from 'react'; -import { fetchElementData } from '../../../utils/fetchElementData'; +import { BioEntity } from '@/types/models'; +import { isReactionBioEntity } from '@/redux/reactions/isReactionBioentity'; interface Props { - target: TargetElement; + target: BioEntity; } export const ElementLink = ({ target }: Props): JSX.Element => { const dispatch = useAppDispatch(); const openedMaps = useAppSelector(mapOpenedMapsSelector); const currentModelId = useAppSelector(mapModelIdSelector); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [data, setData] = useState<BioEntityContent | undefined>(undefined); - const elementId = data?.bioEntity.elementId; const mapsNames = useAppSelector(modelsNameMapSelector); + const isReaction = isReactionBioEntity(target); + const isMapAlreadyOpened = (modelId: number): boolean => openedMaps.some(map => map.modelId === modelId); - const getElementLinkData = async (searchQuery: string): Promise<void> => { - const fetchedData = await fetchElementData(searchQuery).finally(() => setIsLoading(false)); - - if (fetchedData) { - setData(fetchedData); - } - }; - const searchForElementAndOpenDrawer = (): void => { - if (!elementId) return; - - const searchValues = getSearchValuesArrayAndTrimToSeven(elementId); - dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch: false })); + let query = ''; + if (isReaction) { + query = `reaction:${target.id}`; + } else { + query = `element:${target.id}`; + } + const searchValues = getSearchValuesArrayAndTrimToSeven(query); + dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch: true })); dispatch(openSearchDrawerWithSelectedTab(getDefaultSearchTab(searchValues))); }; const openSubmap = (): void => { - if (isMapAlreadyOpened(target.modelId)) { - dispatch(setActiveMap({ modelId: target.modelId })); + if (isMapAlreadyOpened(target.model)) { + dispatch(setActiveMap({ modelId: target.model })); } else { - dispatch( - openMapAndSetActive({ modelId: target.modelId, modelName: mapsNames[target.modelId] }), - ); + dispatch(openMapAndSetActive({ modelId: target.model, modelName: mapsNames[target.model] })); } - if (currentModelId !== target.modelId) { + if (currentModelId !== target.model) { PluginsEventBus.dispatchEvent('onSubmapClose', currentModelId); - PluginsEventBus.dispatchEvent('onSubmapOpen', target.modelId); + PluginsEventBus.dispatchEvent('onSubmapOpen', target.model); } }; @@ -71,21 +62,15 @@ export const ElementLink = ({ target }: Props): JSX.Element => { openSubmap(); }; - useEffect(() => { - getElementLinkData(`${target.id}`); - }, [target.id]); - - if (isLoading || !elementId) { - return <LoadingIndicator />; - } - return ( <button type="button" className="inline-block cursor-pointer underline" onClick={handleElementLinkClick} > - {elementId} + {isReaction && 'Reaction: '} + {!isReaction && 'Element: '} + {target.elementId} </button> ); }; diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.test.tsx index 8c8c0fe7..2e7349c6 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.test.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.test.tsx @@ -1,21 +1,18 @@ -import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, THIRD_ARRAY_ELEMENT } from '@/constants/common'; -import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; -import { apiPath } from '@/redux/apiPath'; import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures'; import { AppDispatch, RootState } from '@/redux/store'; -import { BioEntityContent, TargetElement } from '@/types/models'; -import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; +import { BioEntity } from '@/types/models'; import { InitialStoreState, getReduxStoreWithActionsListener, } from '@/utils/testing/getReduxStoreActionsListener'; import { render, screen, waitFor } from '@testing-library/react'; -import { HttpStatusCode } from 'axios'; import { MockStoreEnhanced } from 'redux-mock-store'; +import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; +import { isReactionBioEntity } from '@/redux/reactions/isReactionBioentity'; import { ElementsOnMapCell } from './ElementsOnMapCell.component'; interface Props { - targets: TargetElement[]; + targets: BioEntity[]; } const renderComponent = ( @@ -36,62 +33,13 @@ const renderComponent = ( ); }; -const mockedAxiosNewClient = mockNetworkNewAPIResponse(); +const elementFixture = { ...bioEntityFixture, idReaction: undefined }; +const reactionFixture = { ...bioEntityFixture, idReaction: '123' }; -const mockTargets = [ - { id: 1, modelId: 2, type: 'target-1' }, - { id: 2, modelId: 3, type: 'target-2' }, - { id: 3, modelId: 4, type: 'target-3' }, -]; - -const getBioEntityContent = (elementId: string): BioEntityContent[] => [ - { - ...bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT], - bioEntity: { - ...bioEntityResponseFixture.content[FIRST_ARRAY_ELEMENT].bioEntity, - elementId, - }, - }, -]; +const mockTargets = [{ ...elementFixture }, { ...reactionFixture }]; describe('ElementsOnMapCell - component', () => { - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: mockTargets[FIRST_ARRAY_ELEMENT].id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityResponseFixture, - content: getBioEntityContent(mockTargets[FIRST_ARRAY_ELEMENT].type), - }); - - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: mockTargets[SECOND_ARRAY_ELEMENT].id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityResponseFixture, - content: getBioEntityContent(mockTargets[SECOND_ARRAY_ELEMENT].type), - }); - - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: mockTargets[THIRD_ARRAY_ELEMENT].id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityResponseFixture, - content: getBioEntityContent(mockTargets[THIRD_ARRAY_ELEMENT].type), - }); - - test.each(mockTargets)('should render correctly', async ({ type }) => { + test.each(mockTargets)('should render correctly', async bioEntity => { renderComponent( { targets: mockTargets, @@ -101,7 +49,9 @@ describe('ElementsOnMapCell - component', () => { await waitFor(() => { // type as elementId - expect(screen.getByText(type)).toBeInTheDocument(); + const isReaction = isReactionBioEntity(bioEntity); + const prefix = isReaction ? 'Reaction: ' : 'Element: '; + expect(screen.getByText(prefix + bioEntity.elementId)).toBeInTheDocument(); }); }); }); diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.tsx index 36cffdd4..13c1269f 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementsOnMapCell.component.tsx @@ -1,9 +1,9 @@ import { ONE } from '@/constants/common'; -import { TargetElement } from '@/types/models'; +import { BioEntity } from '@/types/models'; import { ElementLink } from './ElementLink'; interface Props { - targets: TargetElement[]; + targets: BioEntity[]; } export const ElementsOnMapCell = ({ targets }: Props): JSX.Element => { diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/PublicationsTable.component.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/PublicationsTable.component.tsx index 5e720570..cc0db919 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/PublicationsTable.component.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/PublicationsTable.component.tsx @@ -1,9 +1,9 @@ import { ONE, ZERO } from '@/constants/common'; -import { targetElementSchema } from '@/models/targetElementSchema'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { isLoadingSelector, + pageSizeSelector, paginationSelector, searchValueSelector, selectedModelIdSelector, @@ -23,6 +23,7 @@ import { } from '@tanstack/react-table'; import { useRef, useState } from 'react'; import { z } from 'zod'; +import { bioEntitySchema } from '@/models/bioEntitySchema'; import { ElementsOnMapCell } from './ElementsOnMapCell'; import { FilterBySubmapHeader } from './FilterBySubmapHeader/FilterBySubmapHeader.component'; import { DEFAULT_PAGE_SIZE } from './PublicationsTable.constants'; @@ -73,7 +74,9 @@ const columns = [ cell: ({ getValue }): JSX.Element => { try { const valueObject: unknown = JSON.parse(getValue()); - const targets = z.array(targetElementSchema).parse(valueObject); + // eslint-disable-next-line no-console + console.log(valueObject); + const targets = z.array(bioEntitySchema).parse(valueObject); return <ElementsOnMapCell targets={targets} />; } catch (error) { @@ -95,7 +98,9 @@ type PublicationsTableProps = { export const PublicationsTable = ({ data }: PublicationsTableProps): JSX.Element => { const dispatch = useAppDispatch(); - const pagesCount = useAppSelector(totalSizeSelector); + const totalSize = useAppSelector(totalSizeSelector); + const pageSize = useAppSelector(pageSizeSelector); + const pagesCount = totalSize && pageSize ? Math.ceil(totalSize / pageSize) : ONE; const isPublicationsLoading = useAppSelector(isLoadingSelector); const sortColumn = useAppSelector(sortColumnSelector); const sortOrder = useAppSelector(sortOrderSelector); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts index 20ef29c9..a1dacb26 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts @@ -10,6 +10,7 @@ import { waitFor } from '@testing-library/react'; import { HttpStatusCode } from 'axios'; import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; +import { isReactionBioEntity } from '@/redux/reactions/isReactionBioentity'; import { handleAliasResults } from './handleAliasResults'; jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); @@ -32,7 +33,7 @@ describe('handleAliasResults - util', () => { jest.clearAllMocks(); const bioEntityWithIdReaction = bioEntityResponseFixture.content - .filter(c => Boolean(c.bioEntity.idReaction)) + .filter(c => isReactionBioEntity(c.bioEntity)) ?.map(b => b.bioEntity || { id: ZERO }); mockedAxiosOldClient diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 7fce4e2c..174debec 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -111,7 +111,7 @@ export const apiPath = { getMesh: (meshId: string): string => `mesh/${meshId}`, getTaxonomy: (taxonomyId: string): string => `taxonomy/${taxonomyId}`, getPublications: ({ params, modelId = '*' }: GetPublicationsParams): string => - `/projects/${PROJECT_ID}/models/${modelId}/publications/?${getPublicationsURLSearchParams( + `/projects/${PROJECT_ID}/maps/${modelId}/publications/?${getPublicationsURLSearchParams( params, )}`, registerPluign: (): string => `plugins/`, diff --git a/src/redux/publications/publications.selectors.ts b/src/redux/publications/publications.selectors.ts index 202156a9..f9aa8291 100644 --- a/src/redux/publications/publications.selectors.ts +++ b/src/redux/publications/publications.selectors.ts @@ -20,6 +20,9 @@ export const totalSizeSelector = createSelector( data => data?.totalElements, ); +/** totalSize is number of pages */ +export const pageSizeSelector = createSelector(publicationsDataSelector, data => data?.size); + export const filteredSizeSelector = createSelector( publicationsDataSelector, data => data?.filteredSize, diff --git a/src/redux/reactions/isReactionBioentity.ts b/src/redux/reactions/isReactionBioentity.ts new file mode 100644 index 00000000..e4e673a2 --- /dev/null +++ b/src/redux/reactions/isReactionBioentity.ts @@ -0,0 +1,5 @@ +import { BioEntity } from '@/types/models'; + +export const isReactionBioEntity = (bioEntity: BioEntity): boolean => { + return bioEntity.idReaction !== undefined && bioEntity.idReaction !== null; +}; -- GitLab