diff --git a/.gitignore b/.gitignore index 4f41010036975bd3ca35c49270824ecbabaf2444..a99a9da85b837ce2525e86461d3ba1615c0b2d47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .next .next/ +tsconfig.tsbuildinfo # testing /coverage diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index c160a7712333eb282e933e1b5009ae81b9d4c677..0000000000000000000000000000000000000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000000000000000000000000000000000..3194577c510f4f8e9e5a18a50f8c16c7e2da9305 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,33 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} + +echo 'ðŸ—ï¸ðŸ‘· Styling your project before committing👷â€â™‚ï¸ðŸ—ï¸' +echo 'please be patient, this may take a while...' + +# Check ESLint and Prettier Standards +npm run format || +( + echo '🔨⌠Yoo, you have a problem in your code. Check linter and prettier 🔨⌠+ Run npm run format:fix, add changes and try commit again.'; + false; +) + +# Check tsconfig Standards +npm run check-types || +( + echo '😂⌠Failed type check. 😂⌠+ Please correct the types and try commit again.' + false; +) + +# Check Tests in Jest +yarn run test || +( + echo '🤡⌠Failed tests. 🤡⌠+ Check the test result and fix the tests.' + false; +) + +echo '🎉 No error found: committing this now.... ✨🚀ðŸ„â€â™‚ï¸ðŸ»' diff --git a/.husky/precommit b/.husky/precommit deleted file mode 100644 index bd6c3b94ad0be9acddd25d007ab35d096823a5ec..0000000000000000000000000000000000000000 --- a/.husky/precommit +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -echo 'ðŸ—ï¸ðŸ‘· Styling your project before committing👷â€â™‚ï¸ðŸ—ï¸' -echo 'please be patient, this may take a while...' - -# Check ESLint Standards -npm lint || -( - echo '🔨⌠Yoo, you have a problem in your code. Check linter 🔨⌠- Run yarn lint, add changes and try commit again.'; - false; -) - -echo '🎉 No error found: committing this now.... ✨🚀ðŸ„â€â™‚ï¸ðŸ»' - -npx lint-staged \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 790d099b51f95f09a498568be9d57e750ec8b88e..998d2ae142081f0f35efaa77210c3d0b01b85250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.0.1", - "husky": "^8.0.3", + "husky": "^8.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", diff --git a/package.json b/package.json index c4ee3467a915fbdc7f06a1716ac8ec02826fe8d4..9cc8f727b910279491853415bf79fbf366e95031 100644 --- a/package.json +++ b/package.json @@ -11,24 +11,17 @@ "prettier:ci": "./node_modules/.bin/prettier --check .", "format": "next lint && ./node_modules/.bin/prettier --check .", "format:fix": "next lint --fix && ./node_modules/.bin/prettier --write .", + "check-types": "tsc --pretty --noEmit", "prepare": "husky install", "postinstall": "husky install", - "test": "jest --watch --config ./jest.config.ts", + "test": "jest --config ./jest.config.ts", + "test:watch": "jest --watch --config ./jest.config.ts", "test:ci": "jest --config ./jest.config.ts --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false --passWithNoTests", "test:coverage": "jest --watchAll --coverage --config ./jest.config.ts", "test:coveragee": "jest --coverage", "coverage": "open ./coverage/lcov-report/index.html", "cypress": "cypress open" }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx}": [ - "eslint --fix", - "prettier --config ./.prettierrc.js --write" - ], - "**/*.{css,scss,md,html,json}": [ - "prettier --config ./.prettierrc.js --write" - ] - }, "dependencies": { "@next/font": "^13.5.2", "@reduxjs/toolkit": "^1.9.6", @@ -73,7 +66,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.0.1", - "husky": "^8.0.3", + "husky": "^8.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", diff --git a/pages/redux-api-poc.tsx b/pages/redux-api-poc.tsx new file mode 100644 index 0000000000000000000000000000000000000000..86f369323bd601379d1765822b11e9fd54d178b1 --- /dev/null +++ b/pages/redux-api-poc.tsx @@ -0,0 +1,32 @@ +import { getBioEntityContents } from '@/redux/bioEntityContents/bioEntityContents.thunks'; +import { getDrugs } from '@/redux/drugs/drugs.thunks'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { getMirnas } from '@/redux/mirnas/mirnas.thunks'; +import { selectSearchValue } from '@/redux/search/search.selectors'; +import { setSearchValue } from '@/redux/search/search.slice'; +import { useSelector } from 'react-redux'; + +const ReduxPage = (): JSX.Element => { + const dispatch = useAppDispatch(); + const searchValue = useSelector(selectSearchValue); + + const triggerSyncUpdate = (): void => { + // eslint-disable-next-line prefer-template + const newValue = searchValue + 'test'; + dispatch(setSearchValue(newValue)); + dispatch(getDrugs('aspirin')); + dispatch(getMirnas('hsa-miR-302b-3p')); + dispatch(getBioEntityContents('park7')); + }; + + return ( + <div> + {searchValue} + <button type="button" onClick={triggerSyncUpdate}> + sync update + </button> + </div> + ); +}; + +export default ReduxPage; diff --git a/src/constants/api.ts b/src/constants/api.ts deleted file mode 100644 index d768f3c62c83a2fdc4cb554075cb6e301f1f2584..0000000000000000000000000000000000000000 --- a/src/constants/api.ts +++ /dev/null @@ -1 +0,0 @@ -export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || ''; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..43423ae1b1478dfd342c37c5488a44cc09703006 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,3 @@ +export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || ''; +export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || ''; +export const ZOD_SEED = 997; diff --git a/src/constants/mapId.ts b/src/constants/mapId.ts deleted file mode 100644 index 52dd8ff833cdccb1b05eb2e1ffeb7e2db44282f1..0000000000000000000000000000000000000000 --- a/src/constants/mapId.ts +++ /dev/null @@ -1 +0,0 @@ -export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || ''; diff --git a/src/constants/zodSeed.ts b/src/constants/zodSeed.ts deleted file mode 100644 index 1f6c322d61c027fa0f297a27407c7a498c593822..0000000000000000000000000000000000000000 --- a/src/constants/zodSeed.ts +++ /dev/null @@ -1 +0,0 @@ -export const ZOD_SEED = 997; diff --git a/src/models/bioEntityContentSchema.ts b/src/models/bioEntityContentSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..1eb0d70467b9684f15aeaaedfeea1106e5fbdaa7 --- /dev/null +++ b/src/models/bioEntityContentSchema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const bioEntityContentSchema = z.object({ + id: z.number(), + modelId: z.number(), + type: z.string(), +}); diff --git a/src/models/fixtures/bioEntityContentsFixture.ts b/src/models/fixtures/bioEntityContentsFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..adc441d36ea3283a78f4e9134b1be4cbc8f0455d --- /dev/null +++ b/src/models/fixtures/bioEntityContentsFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; + +export const bioEntityContentsFixture = createFixture(z.array(bioEntityContentSchema), { + seed: ZOD_SEED, + array: { min: 2, max: 2 }, +}); diff --git a/src/models/fixtures/drugFixtures.ts b/src/models/fixtures/drugFixtures.ts index 76c0b54c441b7a61c5e384ed7e10ed07496262db..44c60b79eabff4c457ecd14d1f1c5ec0bb398cf5 100644 --- a/src/models/fixtures/drugFixtures.ts +++ b/src/models/fixtures/drugFixtures.ts @@ -1,8 +1,8 @@ +import { ZOD_SEED } from '@/constants'; +import { drugSchema } from '@/models/drugSchema'; +import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { z } from 'zod'; -import { drugSchema } from '@/models/drugSchema'; -import { ZOD_SEED } from '@/constants/zodSeed'; export const drugsFixture = createFixture(z.array(drugSchema), { seed: ZOD_SEED, diff --git a/src/models/fixtures/mirnasFixture.ts b/src/models/fixtures/mirnasFixture.ts index d0c33263c3f24fda8d49b66e81b4d0fd1c30ed5f..129ee9a7995d9ff56fa8444222c61c0c1ecbe893 100644 --- a/src/models/fixtures/mirnasFixture.ts +++ b/src/models/fixtures/mirnasFixture.ts @@ -1,8 +1,8 @@ +import { ZOD_SEED } from '@/constants'; +import { mirnaSchema } from '@/models/mirnaSchema'; +import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { z } from 'zod'; -import { mirnaSchema } from '@/models/mirnaSchema'; -import { ZOD_SEED } from '@/constants/zodSeed'; export const mirnasFixture = createFixture(z.array(mirnaSchema), { seed: ZOD_SEED, diff --git a/src/queries/getDrugsStringWithQuery.test.ts b/src/queries/getDrugsStringWithQuery.test.ts deleted file mode 100644 index 194f670400940bb8496b36a4a78a451fc1575454..0000000000000000000000000000000000000000 --- a/src/queries/getDrugsStringWithQuery.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; -import { getDrugsStringWithQuery } from './getDrugsStringWithQuery'; - -describe('getDrugsStringWithQuery', () => { - it('should return url string', () => { - expect(getDrugsStringWithQuery('aspirin')).toBe( - `projects/${PROJECT_ID}/drugs:search?query=aspirin`, - ); - }); -}); diff --git a/src/queries/getDrugsStringWithQuery.ts b/src/queries/getDrugsStringWithQuery.ts deleted file mode 100644 index 527b0484b4b1327ee827099eb3bae0d08b7405c3..0000000000000000000000000000000000000000 --- a/src/queries/getDrugsStringWithQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; - -export const getDrugsStringWithQuery = (searchQuery: string): string => - `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`; diff --git a/src/queries/getMirnasStringWithQuery.test.ts b/src/queries/getMirnasStringWithQuery.test.ts deleted file mode 100644 index a56ebeed4619ea7310b226d068d96a70e189b3ef..0000000000000000000000000000000000000000 --- a/src/queries/getMirnasStringWithQuery.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; -import { getMirnasStringWithQuery } from './getMirnasStringWithQuery'; - -describe('getMirnasStringWithQuery', () => { - it('should return url string', () => { - expect(getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe( - `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`, - ); - }); -}); diff --git a/src/queries/getMirnasStringWithQuery.ts b/src/queries/getMirnasStringWithQuery.ts deleted file mode 100644 index c5969da4237fecf6827a80174790898a45499015..0000000000000000000000000000000000000000 --- a/src/queries/getMirnasStringWithQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; - -export const getMirnasStringWithQuery = (searchQuery: string): string => - `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`; diff --git a/src/redux/apiPath.test.ts b/src/redux/apiPath.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca36caaedbba792346899f05ee6bb45010727e3d --- /dev/null +++ b/src/redux/apiPath.test.ts @@ -0,0 +1,22 @@ +import { PROJECT_ID } from '@/constants'; +import { apiPath } from '@/redux/apiPath'; + +describe('api path', () => { + it('should return url string for drugs', () => { + expect(apiPath.getDrugsStringWithQuery('aspirin')).toBe( + `projects/${PROJECT_ID}/drugs:search?query=aspirin`, + ); + }); + + it('should return url string for miRNA', () => { + expect(apiPath.getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe( + `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`, + ); + }); + + it('should return url string for bio entity content', () => { + expect(apiPath.getBioEntityContentsStringWithQuery('park7')).toBe( + `projects/${PROJECT_ID}/models/*/bioEntities:search?query=park7`, + ); + }); +}); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts new file mode 100644 index 0000000000000000000000000000000000000000..d199de1ecbe4405c0af4b766000fff2fb5b84e7f --- /dev/null +++ b/src/redux/apiPath.ts @@ -0,0 +1,10 @@ +import { PROJECT_ID } from '@/constants'; + +export const apiPath = { + getBioEntityContentsStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/models/*/bioEntities:search?query=${searchQuery}`, + getDrugsStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`, + getMirnasStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`, +}; diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..12831d5c7b0c2bb05d54f2ca863fa9fd655ea1f0 --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts @@ -0,0 +1,80 @@ +import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; +import { getBioEntityContents } from './bioEntityContents.thunks'; +import bioEntityContentsReducer from './bioEntityContents.slice'; +import { BioEntityContentsState } from './bioEntityContents.types'; + +const mockedAxiosClient = mockNetworkResponse(); +const SEARCH_QUERY = 'park7'; + +const INITIAL_STATE: BioEntityContentsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +describe('bioEntityContents reducer', () => { + let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('bioEntityContents', bioEntityContentsReducer); + }); + + it('should match initial state', () => { + const action = { type: 'unknown' }; + + expect(bioEntityContentsReducer(undefined, action)).toEqual(INITIAL_STATE); + }); + it('should update store after succesfull getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + const { data, loading, error } = store.getState().bioEntityContents; + + expect(type).toBe('project/getBioEntityContents/fulfilled'); + expect(loading).toEqual('succeeded'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(bioEntityContentsFixture); + }); + + it('should update store after failed getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.NotFound, bioEntityContentsFixture); + + const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + const { data, loading, error } = store.getState().bioEntityContents; + + expect(type).toBe('project/getBioEntityContents/rejected'); + expect(loading).toEqual('failed'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual([]); + }); + + it('should update store on loading getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const bioEntityContentsPromise = store.dispatch(getBioEntityContents(SEARCH_QUERY)); + + const { data, loading } = store.getState().bioEntityContents; + expect(data).toEqual([]); + expect(loading).toEqual('pending'); + + bioEntityContentsPromise.then(() => { + const { data: dataPromiseFulfilled, loading: promiseFulfilled } = + store.getState().bioEntityContents; + + expect(dataPromiseFulfilled).toEqual(bioEntityContentsFixture); + expect(promiseFulfilled).toEqual('succeeded'); + }); + }); +}); diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8806feb7989dbfb6cf7a0d2876711c774feec4d --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.reducers.ts @@ -0,0 +1,19 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { BioEntityContentsState } from './bioEntityContents.types'; +import { getBioEntityContents } from './bioEntityContents.thunks'; + +export const getBioEntityContentsReducer = ( + builder: ActionReducerMapBuilder<BioEntityContentsState>, +): void => { + builder.addCase(getBioEntityContents.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(getBioEntityContents.fulfilled, (state, action) => { + state.data = action.payload; + state.loading = 'succeeded'; + }); + builder.addCase(getBioEntityContents.rejected, state => { + state.loading = 'failed'; + // TODO: error management to be discussed in the team + }); +}; diff --git a/src/redux/bioEntityContents/bioEntityContents.slice.ts b/src/redux/bioEntityContents/bioEntityContents.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..97e3b73cbb92836c41263f9cc4744bf9f756799f --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.slice.ts @@ -0,0 +1,20 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { BioEntityContentsState } from '@/redux/bioEntityContents/bioEntityContents.types'; +import { getBioEntityContentsReducer } from './bioEntityContents.reducers'; + +const initialState: BioEntityContentsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +export const bioEntityContentsSlice = createSlice({ + name: 'bioEntityContents', + initialState, + reducers: {}, + extraReducers: builder => { + getBioEntityContentsReducer(builder); + }, +}); + +export default bioEntityContentsSlice.reducer; diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..540e29ce5181233a2ce2667a19fcfcdb3e6e7135 --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts @@ -0,0 +1,39 @@ +import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; +import { getBioEntityContents } from './bioEntityContents.thunks'; +import contentsReducer from './bioEntityContents.slice'; +import { BioEntityContentsState } from './bioEntityContents.types'; + +const mockedAxiosClient = mockNetworkResponse(); +const SEARCH_QUERY = 'park7'; + +describe('bioEntityContents thunks', () => { + let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('bioEntityContents', contentsReducer); + }); + describe('getBioEntityContents', () => { + it('should return data when data response from API is valid', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + expect(payload).toEqual(bioEntityContentsFixture); + }); + it('should return undefined when data response from API is not valid ', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); + + const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + expect(payload).toEqual(undefined); + }); + }); +}); diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ccda7e246a6dd21568856018632752f1d9390bd --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.thunks.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { BioEntityContent } from '@/types/models'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { apiPath } from '@/redux/apiPath'; + +export const getBioEntityContents = createAsyncThunk( + 'project/getBioEntityContents', + async (searchQuery: string): Promise<BioEntityContent[] | undefined> => { + const response = await axiosInstance.get<BioEntityContent[]>( + apiPath.getBioEntityContentsStringWithQuery(searchQuery), + ); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(bioEntityContentSchema)); + + return isDataValid ? response.data : undefined; + }, +); diff --git a/src/redux/bioEntityContents/bioEntityContents.types.ts b/src/redux/bioEntityContents/bioEntityContents.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..3efecc0f2cb75ea779eba64db65743dcfc8b98c1 --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.types.ts @@ -0,0 +1,4 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { BioEntityContent } from '@/types/models'; + +export type BioEntityContentsState = FetchDataState<BioEntityContent[]>; diff --git a/src/redux/drugs/drugs.reducers.test.ts b/src/redux/drugs/drugs.reducers.test.ts index 465ccf700f0eda9e85bc970bd82492e8a61fc00d..c1cee057ecdec50218f33db8fe6ba96a0c05143b 100644 --- a/src/redux/drugs/drugs.reducers.test.ts +++ b/src/redux/drugs/drugs.reducers.test.ts @@ -5,7 +5,7 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; +import { apiPath } from '@/redux/apiPath'; import { getDrugs } from './drugs.thunks'; import drugsReducer from './drugs.slice'; import { DrugsState } from './drugs.types'; @@ -32,7 +32,7 @@ describe('drugs reducer', () => { }); it('should update store after succesfull getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const { type } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -46,7 +46,7 @@ describe('drugs reducer', () => { it('should update store after failed getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.NotFound, []); const { type } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -60,7 +60,7 @@ describe('drugs reducer', () => { it('should update store on loading getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const drugsPromise = store.dispatch(getDrugs(SEARCH_QUERY)); diff --git a/src/redux/drugs/drugs.thunks.test.ts b/src/redux/drugs/drugs.thunks.test.ts index 97402dddf2facffc9388aa4ddc7386cb23ecb086..514ffccfe9f8b57d1f698a98e48640f35186c130 100644 --- a/src/redux/drugs/drugs.thunks.test.ts +++ b/src/redux/drugs/drugs.thunks.test.ts @@ -1,11 +1,11 @@ import { HttpStatusCode } from 'axios'; import { drugsFixture } from '@/models/fixtures/drugFixtures'; import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; +import { apiPath } from '@/redux/apiPath'; import { getDrugs } from './drugs.thunks'; import drugsReducer from './drugs.slice'; import { DrugsState } from './drugs.types'; @@ -21,7 +21,7 @@ describe('drugs thunks', () => { describe('getDrugs', () => { it('should return data when data response from API is valid', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -29,7 +29,7 @@ describe('drugs thunks', () => { }); it('should return undefined when data response from API is not valid ', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY)); diff --git a/src/redux/drugs/drugs.thunks.ts b/src/redux/drugs/drugs.thunks.ts index cbe4ecc23c73e10b619eeec493590086d858695a..fc10b2de59bb0503f018bb41eeb9b54508b8f721 100644 --- a/src/redux/drugs/drugs.thunks.ts +++ b/src/redux/drugs/drugs.thunks.ts @@ -1,15 +1,15 @@ import { z } from 'zod'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { axiosInstance } from '@/services/api/utils/axiosInstance'; import { Drug } from '@/types/models'; import { drugSchema } from '@/models/drugSchema'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { apiPath } from '@/redux/apiPath'; export const getDrugs = createAsyncThunk( 'project/getDrugs', async (searchQuery: string): Promise<Drug[] | undefined> => { - const response = await axiosInstance.get<Drug[]>(getDrugsStringWithQuery(searchQuery)); + const response = await axiosInstance.get<Drug[]>(apiPath.getDrugsStringWithQuery(searchQuery)); const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema)); diff --git a/src/redux/mirnas/mirnas.reducers.test.ts b/src/redux/mirnas/mirnas.reducers.test.ts index c29544dbf45fac0cd9e7920c922ed25942de8629..5dd6651a862ad42a852dc3b1c0590cdd01e5bdf9 100644 --- a/src/redux/mirnas/mirnas.reducers.test.ts +++ b/src/redux/mirnas/mirnas.reducers.test.ts @@ -4,14 +4,14 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; import { getMirnas } from './mirnas.thunks'; import mirnasReducer from './mirnas.slice'; import { MirnasState } from './mirnas.types'; const mockedAxiosClient = mockNetworkResponse(); -const SEARCH_QUERY = 'aspirin'; +const SEARCH_QUERY = 'hsa-miR-302b-3p'; const INITIAL_STATE: MirnasState = { data: [], @@ -32,7 +32,7 @@ describe('mirnas reducer', () => { }); it('should update store after succesfull getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const { type } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -46,7 +46,7 @@ describe('mirnas reducer', () => { it('should update store after failed getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.NotFound, []); const { type } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -60,7 +60,7 @@ describe('mirnas reducer', () => { it('should update store on loading getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const mirnasPromise = store.dispatch(getMirnas(SEARCH_QUERY)); diff --git a/src/redux/mirnas/mirnas.thunks.test.ts b/src/redux/mirnas/mirnas.thunks.test.ts index 6732a6ab2040d7ff3de684324d190b29d2076c38..2712a5ffa305457a5099f15639fdf3b2e10ac764 100644 --- a/src/redux/mirnas/mirnas.thunks.test.ts +++ b/src/redux/mirnas/mirnas.thunks.test.ts @@ -4,8 +4,8 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; import { getMirnas } from './mirnas.thunks'; import mirnasReducer from './mirnas.slice'; import { MirnasState } from './mirnas.types'; @@ -21,7 +21,7 @@ describe('mirnas thunks', () => { describe('getMirnas', () => { it('should return data when data response from API is valid', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -29,7 +29,7 @@ describe('mirnas thunks', () => { }); it('should return undefined when data response from API is not valid ', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY)); diff --git a/src/redux/mirnas/mirnas.thunks.ts b/src/redux/mirnas/mirnas.thunks.ts index 440fe7a6a8920d913a9f79656ff93218e50228b2..8274e868c7294583d67785c468abce40f94c886c 100644 --- a/src/redux/mirnas/mirnas.thunks.ts +++ b/src/redux/mirnas/mirnas.thunks.ts @@ -4,12 +4,14 @@ import { axiosInstance } from '@/services/api/utils/axiosInstance'; import { Mirna } from '@/types/models'; import { mirnaSchema } from '@/models/mirnaSchema'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; +import { apiPath } from '@/redux/apiPath'; export const getMirnas = createAsyncThunk( 'project/getMirnas', async (searchQuery: string): Promise<Mirna[] | undefined> => { - const response = await axiosInstance.get<Mirna[]>(getMirnasStringWithQuery(searchQuery)); + const response = await axiosInstance.get<Mirna[]>( + apiPath.getMirnasStringWithQuery(searchQuery), + ); const isDataValid = validateDataUsingZodSchema(response.data, z.array(mirnaSchema)); diff --git a/src/services/api/utils/axiosInstance.ts b/src/services/api/utils/axiosInstance.ts index 5e50fba77c865c1e222f51a4c451c9d14197242e..5912be0111ef30130d07af064b423be058b389ec 100644 --- a/src/services/api/utils/axiosInstance.ts +++ b/src/services/api/utils/axiosInstance.ts @@ -1,5 +1,5 @@ +import { BASE_API_URL } from '@/constants'; import axios from 'axios'; -import { BASE_API_URL } from '@/constants/api'; export const axiosInstance = axios.create({ baseURL: BASE_API_URL, diff --git a/src/services/api/utils/useApiQuery.ts b/src/services/api/utils/useApiQuery.ts index d150d65626e1f3f7d086ef39a5a12bc9adc73c0c..917227a6f20639dc621581270ddf53a137f67048 100644 --- a/src/services/api/utils/useApiQuery.ts +++ b/src/services/api/utils/useApiQuery.ts @@ -1,4 +1,4 @@ -import { BASE_API_URL } from '@/constants/api'; +import { BASE_API_URL } from '@/constants'; import { QueryOptions } from '@/types/api'; import useAxios, { UseAxiosResult } from 'axios-hooks'; @@ -19,7 +19,7 @@ export const useApiQuery: UseApiQuery = <TResponse extends AnyZodObject>({ url: `${BASE_API_URL}${path}`, }); - const dataValidation: { success: boolean; error?: ZodError<TResponse> } = useMemo(() => { + const dataValidation: { success: boolean; error?: ZodError } = useMemo(() => { if (!fetchData) { return { success: false, error: undefined }; } diff --git a/src/types/models.ts b/src/types/models.ts index 12d62515c6d9c58620f6a4fb983f5ccf29067d0d..52aca9ac48e7ec7903544dacf3442d6a28ca911e 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -3,6 +3,7 @@ import { drugSchema } from '@/models/drugSchema'; import { organism } from '@/models/organism'; import { projectSchema } from '@/models/project'; import { mirnaSchema } from '@/models/mirnaSchema'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; import { z } from 'zod'; export type Project = z.infer<typeof projectSchema>; @@ -10,3 +11,4 @@ export type Organism = z.infer<typeof organism>; export type Disease = z.infer<typeof disease>; export type Drug = z.infer<typeof drugSchema>; export type Mirna = z.infer<typeof mirnaSchema>; +export type BioEntityContent = z.infer<typeof bioEntityContentSchema>;