From 59d4fd60d874bf1f4babe0103b567e37a813306a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20Miesi=C4=85c?= <tadeusz.miesiac@gmail.com> Date: Fri, 8 Dec 2023 09:36:38 +0100 Subject: [PATCH] feat(configuration options): fetch configuration at start of the app --- src/constants/overlayParticleMock.ts | 137 ++++++++++++++++++ src/models/configurationOptionSchema.ts | 11 ++ src/models/mocks/configurationOptionMock.ts | 47 ++++++ src/redux/apiPath.ts | 1 + .../configuration/configuration.adapter.ts | 18 +++ src/redux/configuration/configuration.mock.ts | 27 ++++ .../configuration/configuration.reducers.ts | 21 +++ .../configuration/configuration.slice.ts | 14 ++ .../configuration/configuration.thunks.ts | 23 +++ src/redux/root/init.thunks.ts | 2 + src/redux/root/root.fixtures.ts | 2 + src/redux/store.ts | 2 + src/types/loadingState.ts | 3 + src/types/models.ts | 2 + todo.txt | 2 + 15 files changed, 312 insertions(+) create mode 100644 src/constants/overlayParticleMock.ts create mode 100644 src/models/configurationOptionSchema.ts create mode 100644 src/models/mocks/configurationOptionMock.ts create mode 100644 src/redux/configuration/configuration.adapter.ts create mode 100644 src/redux/configuration/configuration.mock.ts create mode 100644 src/redux/configuration/configuration.reducers.ts create mode 100644 src/redux/configuration/configuration.slice.ts create mode 100644 src/redux/configuration/configuration.thunks.ts create mode 100644 todo.txt diff --git a/src/constants/overlayParticleMock.ts b/src/constants/overlayParticleMock.ts new file mode 100644 index 00000000..8ec5ab00 --- /dev/null +++ b/src/constants/overlayParticleMock.ts @@ -0,0 +1,137 @@ +// const overlayParticleMock = { +// left: { +// // molekuła na której jest prostokąt +// id: 38929, +// model: 52, +// glyph: null, +// submodel: null, +// compartment: 46645, +// elementId: 'path_0_sa8410', +// x: 15181.0, // wspolrzedne do lewego gornego naroznika +// y: 6976.0, // wspolrzedne do lewego gornego naroznika +// z: 4528, +// width: 80.0, +// height: 40.0, +// fontSize: 12.0, +// fontColor: { +// alpha: 255, +// rgb: -16777216, +// }, +// fillColor: { +// alpha: 255, +// rgb: -3342388, // wartosc hexa przeksztalcona na wartosc binarna? rgb +// }, +// borderColor: { +// alpha: 255, +// rgb: -16777216, +// }, +// visibilityLevel: '3', +// transparencyLevel: '0', +// notes: '', +// symbol: 'ATP6V0B', +// fullName: 'ATPase H+ transporting V0 subunit b', +// abbreviation: null, +// formula: null, +// name: 'ATP6V0B', +// nameX: 15181.0, +// nameY: 6976.0, +// nameWidth: 80.0, +// nameHeight: 40.0, +// nameVerticalAlign: 'MIDDLE', +// nameHorizontalAlign: 'CENTER', +// synonyms: ['HATPL', 'VMA16'], +// formerSymbols: ['ATP6F'], +// activity: false, +// lineWidth: 1.0, +// complex: 38928, +// initialAmount: null, +// charge: null, +// initialConcentration: 0.0, +// onlySubstanceUnits: false, +// homodimer: 1, +// hypothetical: null, +// boundaryCondition: false, +// constant: false, +// modificationResidues: [], +// stringType: 'Protein', +// substanceUnits: null, +// references: [ +// { +// link: 'https://www.ncbi.nlm.nih.gov/gene/533', +// type: 'ENTREZ', +// resource: '533', +// id: 178289, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// { +// link: 'https://www.ncbi.nlm.nih.gov/protein/NM_004047', +// type: 'REFSEQ', +// resource: 'NM_004047', +// id: 178290, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// { +// link: 'https://www.genenames.org/cgi-bin/gene_symbol_report?match=ATP6V0B', +// type: 'HGNC_SYMBOL', +// resource: 'ATP6V0B', +// id: 178291, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// { +// link: 'https://www.ensembl.org/id/ENSG00000117410', +// type: 'ENSEMBL', +// resource: 'ENSG00000117410', +// id: 178292, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// { +// link: 'https://purl.uniprot.org/uniprot/Q99437', +// type: 'UNIPROT', +// resource: 'Q99437', +// id: 178293, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// { +// link: 'https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/861', +// type: 'HGNC', +// resource: '861', +// id: 178294, +// annotatorClassName: 'lcsb.mapviewer.annotation.services.annotators.HgncAnnotator', +// }, +// ], +// }, +// right: { +// // definiuje prostokat, na jakiej podstawie zostalo matchowane, moze byc nazwa, identyfikator, nazwa+mapa +// id: 9558, +// name: 'ATP6V0B', +// modelName: null, +// elementId: null, +// reverseReaction: null, +// lineWidth: null, +// // bede mial albo wartosc albo kolor, jak bede mial oba (raczej nie bedzie) to jest to bug +// value: -0.493438352, // musze wartość zmapowac na kolor -> -1 to np czerwony a 1 to niebieski. Nie mam koloru. W configuracji minervy jest legenda z kolorem dla -1, 0 i 1 +// color: null, +// // kiedy nie ma wartości value i color to z configuracji jest brany element "no color val" dla koloru +// description: null, +// }, +// }; + +// const color = { +// // definiuje prostokat, na jakiej podstawie zostalo matchowane, moze byc nazwa, identyfikator, nazwa+mapa +// id: 9558, +// name: 'ATP6V0B', +// modelName: null, +// elementId: null, +// reverseReaction: null, +// lineWidth: null, +// // bede mial albo wartosc albo kolor, jak bede mial oba (raczej nie bedzie) to jest to bug +// value: null, +// color: { +// alpha: 255, +// rgb: -16711936, +// }, +// description: null, +// }; + +// // pole lines jest od reakcji - polegac na tym polu zeby odroznic molekuly od reakcji +// // porozmawiac z Markiem o sumowaniu overlays na przycisku "go to submap" diff --git a/src/models/configurationOptionSchema.ts b/src/models/configurationOptionSchema.ts new file mode 100644 index 00000000..9268478c --- /dev/null +++ b/src/models/configurationOptionSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const configurationOptionSchema = z.object({ + idObject: z.number(), + type: z.string(), + valueType: z.string(), + commonName: z.string(), + isServerSide: z.boolean(), + value: z.string().optional(), + group: z.string(), +}); diff --git a/src/models/mocks/configurationOptionMock.ts b/src/models/mocks/configurationOptionMock.ts new file mode 100644 index 00000000..f658402e --- /dev/null +++ b/src/models/mocks/configurationOptionMock.ts @@ -0,0 +1,47 @@ +import { ConfigurationOption } from '@/types/models'; + +export const CONFIGURATION_OPTIONS_TYPES_MOCK: string[] = [ + 'MIN_COLOR_VAL', + 'MAX_COLOR_VAL', + 'SIMPLE_COLOR_VAL', + 'NEUTRAL_COLOR_VAL', +]; + +export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [ + { + idObject: 29, + type: 'MIN_COLOR_VAL', + valueType: 'COLOR', + commonName: 'Overlay color for negative values', + isServerSide: false, + value: 'FF0000', + group: 'Overlays', + }, + { + idObject: 30, + type: 'MAX_COLOR_VAL', + valueType: 'COLOR', + commonName: 'Overlay color for postive values', + isServerSide: false, + value: '0000FF', + group: 'Overlays', + }, + { + idObject: 31, + type: 'SIMPLE_COLOR_VAL', + valueType: 'COLOR', + commonName: 'Overlay color when no values are defined', + isServerSide: false, + value: '00FF00', + group: 'Overlays', + }, + { + idObject: 32, + type: 'NEUTRAL_COLOR_VAL', + valueType: 'COLOR', + commonName: 'Overlay color for value=0', + isServerSide: false, + value: 'FFFFFF', + group: 'Overlays', + }, +]; diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 42711404..70ad3379 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -29,4 +29,5 @@ export const apiPath = { getAllBackgroundsByProjectIdQuery: (projectId: string): string => `projects/${projectId}/backgrounds/`, getProjectById: (projectId: string): string => `projects/${projectId}`, + getConfigurationOptions: (): string => 'configuration/options/', }; diff --git a/src/redux/configuration/configuration.adapter.ts b/src/redux/configuration/configuration.adapter.ts new file mode 100644 index 00000000..cb3c59be --- /dev/null +++ b/src/redux/configuration/configuration.adapter.ts @@ -0,0 +1,18 @@ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { Loading } from '@/types/loadingState'; +import { ConfigurationOption } from '@/types/models'; +import { createEntityAdapter } from '@reduxjs/toolkit'; + +export const configurationAdapter = createEntityAdapter<ConfigurationOption>({ + selectId: option => option.type, +}); + +const REQUEST_INITIAL_STATUS: { loading: Loading; error: Error } = { + loading: 'idle', + error: DEFAULT_ERROR, +}; + +export const CONFIGURATION_INITIAL_STATE = + configurationAdapter.getInitialState(REQUEST_INITIAL_STATUS); + +export type ConfigurationState = typeof CONFIGURATION_INITIAL_STATE; diff --git a/src/redux/configuration/configuration.mock.ts b/src/redux/configuration/configuration.mock.ts new file mode 100644 index 00000000..ce8f052d --- /dev/null +++ b/src/redux/configuration/configuration.mock.ts @@ -0,0 +1,27 @@ +/* eslint-disable no-magic-numbers */ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { + CONFIGURATION_OPTIONS_TYPES_MOCK, + CONFIGURATION_OPTIONS_COLOURS_MOCK, +} from '@/models/mocks/configurationOptionMock'; +import { ConfigurationState } from './configuration.adapter'; + +export const CONFIGURATION_INITIAL_STORE_MOCK: ConfigurationState = { + ids: [], + entities: {}, + loading: 'idle', + error: DEFAULT_ERROR, +}; + +/** IMPORTANT MOCK IDS MUST MATCH KEYS IN ENTITIES */ +export const CONFIGURATION_INITIAL_STORE_MOCKS: ConfigurationState = { + ids: CONFIGURATION_OPTIONS_TYPES_MOCK, + entities: { + [CONFIGURATION_OPTIONS_TYPES_MOCK[0]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[0], + [CONFIGURATION_OPTIONS_TYPES_MOCK[1]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[1], + [CONFIGURATION_OPTIONS_TYPES_MOCK[2]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[2], + [CONFIGURATION_OPTIONS_TYPES_MOCK[3]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[3], + }, + loading: 'idle', + error: DEFAULT_ERROR, +}; diff --git a/src/redux/configuration/configuration.reducers.ts b/src/redux/configuration/configuration.reducers.ts new file mode 100644 index 00000000..01cd1fe5 --- /dev/null +++ b/src/redux/configuration/configuration.reducers.ts @@ -0,0 +1,21 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { getConfigurationOptions } from './configuration.thunks'; +import { ConfigurationState, configurationAdapter } from './configuration.adapter'; + +export const getConfigurationOptionsReducer = ( + builder: ActionReducerMapBuilder<ConfigurationState>, +): void => { + builder.addCase(getConfigurationOptions.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(getConfigurationOptions.fulfilled, (state, action) => { + if (action.payload) { + state.loading = 'succeeded'; + configurationAdapter.addMany(state, action.payload); + } + }); + builder.addCase(getConfigurationOptions.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/configuration/configuration.slice.ts b/src/redux/configuration/configuration.slice.ts new file mode 100644 index 00000000..4bf43488 --- /dev/null +++ b/src/redux/configuration/configuration.slice.ts @@ -0,0 +1,14 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { getConfigurationOptionsReducer } from './configuration.reducers'; +import { CONFIGURATION_INITIAL_STATE } from './configuration.adapter'; + +export const configurationSlice = createSlice({ + name: 'configuration', + initialState: CONFIGURATION_INITIAL_STATE, + reducers: {}, + extraReducers: builder => { + getConfigurationOptionsReducer(builder); + }, +}); + +export default configurationSlice.reducer; diff --git a/src/redux/configuration/configuration.thunks.ts b/src/redux/configuration/configuration.thunks.ts new file mode 100644 index 00000000..ad3812bb --- /dev/null +++ b/src/redux/configuration/configuration.thunks.ts @@ -0,0 +1,23 @@ +import { ConfigurationOption } from '@/types/models'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { z } from 'zod'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { configurationOptionSchema } from '@/models/configurationOptionSchema'; +import { apiPath } from '../apiPath'; + +export const getConfigurationOptions = createAsyncThunk( + 'configuration/getConfigurationOptions', + async (): Promise<ConfigurationOption[] | undefined> => { + const response = await axiosInstance.get<ConfigurationOption[]>( + apiPath.getConfigurationOptions(), + ); + + const isDataValid = validateDataUsingZodSchema( + response.data, + z.array(configurationOptionSchema), + ); + + return isDataValid ? response.data : undefined; + }, +); diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts index b2c3ef2a..6ac3a819 100644 --- a/src/redux/root/init.thunks.ts +++ b/src/redux/root/init.thunks.ts @@ -16,6 +16,7 @@ import { } from '../map/map.thunks'; import { getSearchData } from '../search/search.thunks'; import { setPerfectMatch } from '../search/search.slice'; +import { getConfigurationOptions } from '../configuration/configuration.thunks'; interface InitializeAppParams { queryData: QueryData; @@ -28,6 +29,7 @@ export const fetchInitialAppData = createAsyncThunk< >('appInit/fetchInitialAppData', async ({ queryData }, { dispatch }): Promise<void> => { /** Fetch all data required for renderin map */ await Promise.all([ + dispatch(getConfigurationOptions()), dispatch(getProjectById(PROJECT_ID)), dispatch(getAllBackgroundsByProjectId(PROJECT_ID)), dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)), diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts index bf70be50..2f5c7a4d 100644 --- a/src/redux/root/root.fixtures.ts +++ b/src/redux/root/root.fixtures.ts @@ -1,6 +1,7 @@ import { BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock'; import { BIOENTITY_INITIAL_STATE_MOCK } from '../bioEntity/bioEntity.mock'; import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock'; +import { CONFIGURATION_INITIAL_STATE } from '../configuration/configuration.adapter'; import { initialStateFixture as drawerInitialStateMock } from '../drawer/drawerFixture'; import { DRUGS_INITIAL_STATE_MOCK } from '../drugs/drugs.mock'; import { initialMapStateFixture } from '../map/map.fixtures'; @@ -23,4 +24,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = { map: initialMapStateFixture, overlays: OVERLAYS_INITIAL_STATE_MOCK, reactions: REACTIONS_STATE_INITIAL_MOCK, + configuration: CONFIGURATION_INITIAL_STATE, }; diff --git a/src/redux/store.ts b/src/redux/store.ts index d98c85c3..7596b8cd 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -9,6 +9,7 @@ import overlaysReducer from '@/redux/overlays/overlays.slice'; import projectReducer from '@/redux/project/project.slice'; import reactionsReducer from '@/redux/reactions/reactions.slice'; import searchReducer from '@/redux/search/search.slice'; +import configurationReducer from '@/redux/configuration/configuration.slice'; import { AnyAction, ListenerEffectAPI, @@ -30,6 +31,7 @@ export const reducers = { overlays: overlaysReducer, models: modelsReducer, reactions: reactionsReducer, + configuration: configurationReducer, }; export const middlewares = [mapListenerMiddleware.middleware]; diff --git a/src/types/loadingState.ts b/src/types/loadingState.ts index 12859c95..d21ff588 100644 --- a/src/types/loadingState.ts +++ b/src/types/loadingState.ts @@ -1 +1,4 @@ export type Loading = 'idle' | 'pending' | 'succeeded' | 'failed'; +export interface LoadingInterface { + loading: 'idle' | 'pending' | 'succeeded' | 'failed'; +} diff --git a/src/types/models.ts b/src/types/models.ts index f83079f1..f8e25e6c 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -2,6 +2,7 @@ import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; import { bioEntitySchema } from '@/models/bioEntitySchema'; import { chemicalSchema } from '@/models/chemicalSchema'; +import { configurationOptionSchema } from '@/models/configurationOptionSchema'; import { disease } from '@/models/disease'; import { drugSchema } from '@/models/drugSchema'; import { elementSearchResult, elementSearchResultType } from '@/models/elementSearchResult'; @@ -35,3 +36,4 @@ export type Reference = z.infer<typeof referenceSchema>; export type ReactionLine = z.infer<typeof reactionLineSchema>; export type ElementSearchResult = z.infer<typeof elementSearchResult>; export type ElementSearchResultType = z.infer<typeof elementSearchResultType>; +export type ConfigurationOption = z.infer<typeof configurationOptionSchema>; diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..4f1c4bba --- /dev/null +++ b/todo.txt @@ -0,0 +1,2 @@ + - render overlays for map for single element + - \ No newline at end of file -- GitLab