diff --git a/src/components/FunctionalArea/NavBar/NavBar.component.tsx b/src/components/FunctionalArea/NavBar/NavBar.component.tsx index fbc5c000ee41e1dd810219d5d9cf181263fc02a7..830131c1ac047fd046c01ee9e585b48d876cf8c5 100644 --- a/src/components/FunctionalArea/NavBar/NavBar.component.tsx +++ b/src/components/FunctionalArea/NavBar/NavBar.component.tsx @@ -15,7 +15,7 @@ export const NavBar = (): JSX.Element => { }; const openDrawerPlugins = (): void => { - dispatch(openDrawer('plugins')); + dispatch(openDrawer('available-plugins')); }; const openDrawerExport = (): void => { diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..06d70d35abe59da6cf05b3c3700d991746cc3db8 --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx @@ -0,0 +1,53 @@ +import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock'; +import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures'; +import { StoreType } from '@/redux/store'; +import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { AvailablePluginsDrawer } from './AvailablePluginsDrawer.component'; + +const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStore); + return ( + render( + <Wrapper> + <AvailablePluginsDrawer /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('AvailablePluginsDrawer - component', () => { + describe('when always', () => { + it('should render drawer heading', () => { + renderComponent(INITIAL_STORE_STATE_MOCK); + const drawerTitle = screen.getByText('Available plugins'); + expect(drawerTitle).toBeInTheDocument(); + }); + + it('should render load plugin from url', () => { + renderComponent(INITIAL_STORE_STATE_MOCK); + const loadPluginFromUrlInput = screen.getByTestId('load-plugin-input-url'); + expect(loadPluginFromUrlInput).toBeInTheDocument(); + }); + + it.each(PLUGINS_MOCK)('should render render all public plugins', currentPlugin => { + renderComponent({ + ...INITIAL_STORE_STATE_MOCK, + plugins: { + ...INITIAL_STORE_STATE_MOCK.plugins, + list: { + ...INITIAL_STORE_STATE_MOCK.plugins.list, + data: PLUGINS_MOCK, + }, + }, + }); + + const pluginLabel = screen.getByText(currentPlugin.name); + expect(pluginLabel).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1343d91bbf51f36f926c2af248d596790155f04 --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.tsx @@ -0,0 +1,21 @@ +import { publicPluginsListSelector } from '@/redux/plugins/plugins.selectors'; +import { DrawerHeading } from '@/shared/DrawerHeading'; +import { useSelector } from 'react-redux'; +import { LoadPlugin } from './LoadPlugin'; +import { LoadPluginFromUrl } from './LoadPluginFromUrl'; + +export const AvailablePluginsDrawer = (): JSX.Element => { + const publicPlugins = useSelector(publicPluginsListSelector); + + return ( + <div className="h-full max-h-full" data-testid="available-plugins-drawer"> + <DrawerHeading title="Available plugins" /> + <div className="flex flex-col gap-6 p-6"> + <LoadPluginFromUrl /> + {publicPlugins.map(plugin => ( + <LoadPlugin key={plugin.hash} plugin={plugin} /> + ))} + </div> + </div> + ); +}; diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..92c659ac76bde94184d4324be245989f7c8f4dec --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx @@ -0,0 +1,29 @@ +import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; +import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock'; +import { render, screen } from '@testing-library/react'; +import { LoadPlugin, Props } from './LoadPlugin.component'; + +const renderComponent = ({ plugin }: Props): void => { + render(<LoadPlugin plugin={plugin} />); +}; + +describe('LoadPlugin - component', () => { + describe('when always', () => { + const plugin = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT]; + + it('renders plugin name', () => { + renderComponent({ plugin }); + + const title = screen.getByText(plugin.name); + expect(title).toBeInTheDocument(); + }); + + it('renders plugin load button', () => { + renderComponent({ plugin }); + + const loadButton = screen.getByText('Load'); + expect(loadButton.tagName).toBe('BUTTON'); + expect(loadButton).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fee403ca18cb34195aaf42a559ba618eb68f821e --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.tsx @@ -0,0 +1,25 @@ +import { Button } from '@/shared/Button'; +import { MinervaPlugin } from '@/types/models'; + +export interface Props { + plugin: MinervaPlugin; +} + +export const LoadPlugin = ({ plugin }: Props): JSX.Element => { + const handleLoadPlugin = (): void => { + // TODO: handleLoadPlugin + }; + + return ( + <div className="flex w-full items-center justify-between"> + <span className="text-cetacean-blue">{plugin.name}</span> + <Button + variantStyles="secondary" + className="h-10 self-end rounded-e rounded-s" + onClick={handleLoadPlugin} + > + Load + </Button> + </div> + ); +}; diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/index.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e27e6ba35e4443188bf5a6e08a655ddb97b3262c --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/index.ts @@ -0,0 +1 @@ +export { LoadPlugin } from './LoadPlugin.component'; diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e1d83a8ba9fde4258d36ad1d5df8629ac583ce57 --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; +import { LoadPluginFromUrl } from './LoadPluginFromUrl.component'; + +const renderComponent = (): void => { + render(<LoadPluginFromUrl />); +}; + +describe('LoadPluginFromUrl - component', () => { + describe('when always', () => { + it('renders plugin input label', () => { + renderComponent(); + + const pluginInputLabel = screen.getByLabelText('URL:'); + expect(pluginInputLabel).toBeInTheDocument(); + }); + + it('renders plugin input', () => { + renderComponent(); + + const pluginInput = screen.getByTestId('load-plugin-input-url'); + expect(pluginInput).toBeInTheDocument(); + }); + + it('renders plugin load button', () => { + renderComponent(); + + const loadButton = screen.getByText('Load'); + expect(loadButton.tagName).toBe('BUTTON'); + expect(loadButton).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..de486ad2bb19462bca3674a24f9a72a126d1f75c --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/shared/Button'; +import { useState } from 'react'; + +export const LoadPluginFromUrl = (): JSX.Element => { + const [url, setUrl] = useState<string>(''); + + const handleLoadPlugin = (): void => { + // TODO: handleLoadPlugin + }; + + return ( + <div className="flex w-full"> + <label className="flex w-full flex-col gap-2 text-sm text-cetacean-blue"> + <span>URL:</span> + <input + className="h-10 w-full bg-cultured p-3" + type="url" + value={url} + onChange={(e): void => setUrl(e.target.value)} + data-testid="load-plugin-input-url" + /> + </label> + <Button + variantStyles="secondary" + className="h-10 self-end rounded-e rounded-s" + onClick={handleLoadPlugin} + > + Load + </Button> + </div> + ); +}; diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/index.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..cedb0f665553574022fb55f4f00ef9678b546f1d --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/index.ts @@ -0,0 +1 @@ +export { LoadPluginFromUrl } from './LoadPluginFromUrl.component'; diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/index.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..85f2fe7f30853779b57a030199940cd3055f8e3a --- /dev/null +++ b/src/components/Map/Drawer/AvailablePluginsDrawer/index.ts @@ -0,0 +1 @@ +export { AvailablePluginsDrawer } from './AvailablePluginsDrawer.component'; diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index 098c4f989ec233798af6de0addfbb28fca84b02e..de1aa94ea44f6f9e2203ea3a386e373cbce620d6 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -2,13 +2,14 @@ import { DRAWER_ROLE } from '@/components/Map/Drawer/Drawer.constants'; import { drawerSelector } from '@/redux/drawer/drawer.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { twMerge } from 'tailwind-merge'; -import { ReactionDrawer } from './ReactionDrawer'; -import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; -import { SubmapsDrawer } from './SubmapsDrawer'; -import { OverlaysDrawer } from './OverlaysDrawer'; +import { AvailablePluginsDrawer } from './AvailablePluginsDrawer'; import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component'; import { ExportDrawer } from './ExportDrawer'; +import { OverlaysDrawer } from './OverlaysDrawer'; import { ProjectInfoDrawer } from './ProjectInfoDrawer'; +import { ReactionDrawer } from './ReactionDrawer'; +import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; +import { SubmapsDrawer } from './SubmapsDrawer'; export const Drawer = (): JSX.Element => { const { isOpen, drawerName } = useAppSelector(drawerSelector); @@ -28,6 +29,7 @@ export const Drawer = (): JSX.Element => { {isOpen && drawerName === 'bio-entity' && <BioEntityDrawer />} {isOpen && drawerName === 'project-info' && <ProjectInfoDrawer />} {isOpen && drawerName === 'export' && <ExportDrawer />} + {isOpen && drawerName === 'available-plugins' && <AvailablePluginsDrawer />} </div> ); }; diff --git a/src/models/mocks/pluginsMock.ts b/src/models/mocks/pluginsMock.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5c91eafdb32833dff36caedddfc4b85d9bac2f6 --- /dev/null +++ b/src/models/mocks/pluginsMock.ts @@ -0,0 +1,44 @@ +import { MinervaPlugin } from '@/types/models'; + +export const PLUGINS_MOCK: MinervaPlugin[] = [ + { + hash: '5e3fcb59588cc311ef9839feea6382eb', + name: 'Disease-variant associations', + version: '1.0.0', + isPublic: true, + isDefault: false, + urls: ['https://minerva-service.lcsb.uni.lu/plugins/disease-associations/plugin.js'], + }, + { + hash: '20df86476c311824bbfe73d1034af89e', + name: 'GSEA', + version: '0.9.2', + isPublic: true, + isDefault: false, + urls: ['https://minerva-service.lcsb.uni.lu/plugins/gsea/plugin.js'], + }, + { + hash: '5314b9f996e56e67f0dad65e7df8b73b', + name: 'PD map guide', + version: '1.0.2', + isPublic: true, + isDefault: false, + urls: ['https://minerva-service.lcsb.uni.lu/plugins/guide/plugin.js'], + }, + { + hash: 'b85ae2f4cd67736489b5fd2b635b1013', + name: 'Map exploation', + version: '1.0.0', + isPublic: true, + isDefault: false, + urls: ['https://minerva-service.lcsb.uni.lu/plugins/exploration/plugin.js'], + }, + { + hash: '77c32edf387652dfaad8a20f2a0ce76b', + name: 'Drug reactions', + version: '1.0.0', + isPublic: true, + isDefault: false, + urls: ['https://minerva-service.lcsb.uni.lu/plugins/drug-reactions/plugin.js'], + }, +]; diff --git a/src/models/pluginSchema.ts b/src/models/pluginSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..0204b6f05d9e797c3cbad124eb063222bcc3e6f3 --- /dev/null +++ b/src/models/pluginSchema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const pluginSchema = z.object({ + hash: z.string(), + name: z.string(), + version: z.string(), + isPublic: z.boolean(), + isDefault: z.boolean(), + urls: z.array(z.string()), +}); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 212660e5ee66c695f389fd6c49b7168b6ec78ecb..0c592bc7c5b8895dde55a87d0b40051491f7c7f6 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -1,6 +1,6 @@ import { PROJECT_ID } from '@/constants'; -import { PerfectSearchParams } from '@/types/search'; import { Point } from '@/types/map'; +import { PerfectSearchParams } from '@/types/search'; export const apiPath = { getBioEntityContentsStringWithQuery: ({ @@ -63,4 +63,5 @@ export const apiPath = { getSourceFile: (): string => `/projects/${PROJECT_ID}:downloadSource`, getMesh: (meshId: string): string => `mesh/${meshId}`, getTaxonomy: (taxonomyId: string): string => `taxonomy/${taxonomyId}`, + getAllPlugins: (): string => `/plugins/`, }; diff --git a/src/redux/plugins/plugins.constants.ts b/src/redux/plugins/plugins.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..0aa77a3a906e4ecbfaf61cd980008a2a1ed7a397 --- /dev/null +++ b/src/redux/plugins/plugins.constants.ts @@ -0,0 +1,9 @@ +import { PluginsState } from './plugins.types'; + +export const PLUGINS_INITIAL_STATE: PluginsState = { + list: { + data: [], + loading: 'idle', + error: { name: '', message: '' }, + }, +}; diff --git a/src/redux/plugins/plugins.mock.ts b/src/redux/plugins/plugins.mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..2322bed65b3f54bb96ceb55ec122d73f93650654 --- /dev/null +++ b/src/redux/plugins/plugins.mock.ts @@ -0,0 +1,12 @@ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { PluginsList, PluginsState } from './plugins.types'; + +export const PLUGINS_INITIAL_STATE_LIST_MOCK: PluginsList = { + data: undefined, + loading: 'idle', + error: DEFAULT_ERROR, +}; + +export const PLUGINS_INITIAL_STATE_MOCK: PluginsState = { + list: PLUGINS_INITIAL_STATE_LIST_MOCK, +}; diff --git a/src/redux/plugins/plugins.reducers.ts b/src/redux/plugins/plugins.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd5f9fc6614702add5c78b11ccbf1811ad4bab65 --- /dev/null +++ b/src/redux/plugins/plugins.reducers.ts @@ -0,0 +1,17 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { getAllPlugins } from './plugins.thunks'; +import { PluginsState } from './plugins.types'; + +export const getAllPluginsReducer = (builder: ActionReducerMapBuilder<PluginsState>): void => { + builder.addCase(getAllPlugins.pending, state => { + state.list.loading = 'pending'; + }); + builder.addCase(getAllPlugins.fulfilled, (state, action) => { + state.list.data = action.payload || []; + state.list.loading = 'succeeded'; + }); + builder.addCase(getAllPlugins.rejected, state => { + state.list.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/plugins/plugins.selectors.ts b/src/redux/plugins/plugins.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ece3dd2e529230dd8f85c413703675ebd7de90d --- /dev/null +++ b/src/redux/plugins/plugins.selectors.ts @@ -0,0 +1,19 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { rootSelector } from '../root/root.selectors'; + +export const pluginsSelector = createSelector(rootSelector, state => state.plugins); + +export const pluginsListSelector = createSelector(pluginsSelector, plugins => { + return plugins.list; +}); + +export const pluginsListDataSelector = createSelector(pluginsListSelector, pluginsList => { + return pluginsList.data; +}); + +export const publicPluginsListSelector = createSelector( + pluginsListDataSelector, + pluginsListData => { + return (pluginsListData || []).filter(plugin => plugin.isPublic); + }, +); diff --git a/src/redux/plugins/plugins.slice.ts b/src/redux/plugins/plugins.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ac4e01130e47b994104373c7cfa0c41ad20b8ac --- /dev/null +++ b/src/redux/plugins/plugins.slice.ts @@ -0,0 +1,14 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { PLUGINS_INITIAL_STATE } from './plugins.constants'; +import { getAllPluginsReducer } from './plugins.reducers'; + +const pluginsState = createSlice({ + name: 'plugins', + initialState: PLUGINS_INITIAL_STATE, + reducers: {}, + extraReducers: builder => { + getAllPluginsReducer(builder); + }, +}); + +export default pluginsState.reducer; diff --git a/src/redux/plugins/plugins.thunks.ts b/src/redux/plugins/plugins.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..732cfdcb4fcadb1b83a804bb51c8f581d2269f50 --- /dev/null +++ b/src/redux/plugins/plugins.thunks.ts @@ -0,0 +1,18 @@ +import { pluginSchema } from '@/models/pluginSchema'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { MinervaPlugin } from '@/types/models'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { z } from 'zod'; +import { apiPath } from '../apiPath'; + +export const getAllPlugins = createAsyncThunk( + 'plugins/getAllPlugins', + async (): Promise<MinervaPlugin[]> => { + const response = await axiosInstance.get<MinervaPlugin[]>(apiPath.getAllPlugins()); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(pluginSchema)); + + return isDataValid ? response.data : []; + }, +); diff --git a/src/redux/plugins/plugins.types.ts b/src/redux/plugins/plugins.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7d7ae4c3ffdcf7c5477ddcb3bfa0408e7c2be77 --- /dev/null +++ b/src/redux/plugins/plugins.types.ts @@ -0,0 +1,8 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { MinervaPlugin } from '@/types/models'; + +export type PluginsList = FetchDataState<MinervaPlugin[]>; + +export type PluginsState = { + list: PluginsList; +}; diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts index c4ac19274381cdffa49913eff2a4e40ed79960e7..f6e14e6a928ae7e738fc3a267c10cd7592813379 100644 --- a/src/redux/root/init.thunks.ts +++ b/src/redux/root/init.thunks.ts @@ -1,25 +1,26 @@ -import { openSearchDrawerWithSelectedTab } from '@/redux/drawer/drawer.slice'; -import { createAsyncThunk } from '@reduxjs/toolkit'; +import { getDefaultSearchTab } from '@/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils'; import { PROJECT_ID } from '@/constants'; +import { openSearchDrawerWithSelectedTab } from '@/redux/drawer/drawer.slice'; import { AppDispatch } from '@/redux/store'; import { QueryData } from '@/types/query'; -import { getDefaultSearchTab } from '@/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils'; +import { createAsyncThunk } from '@reduxjs/toolkit'; import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks'; -import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks'; -import { getModels } from '../models/models.thunks'; -import { getProjectById } from '../project/project.thunks'; +import { getConfiguration, getConfigurationOptions } from '../configuration/configuration.thunks'; import { initMapBackground, initMapPosition, initMapSizeAndModelId, initOpenedMaps, } from '../map/map.thunks'; -import { getSearchData } from '../search/search.thunks'; -import { setPerfectMatch } from '../search/search.slice'; -import { getSessionValid } from '../user/user.thunks'; +import { getModels } from '../models/models.thunks'; import { getInitOverlays } from '../overlayBioEntity/overlayBioEntity.thunk'; -import { getConfiguration, getConfigurationOptions } from '../configuration/configuration.thunks'; +import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks'; +import { getAllPlugins } from '../plugins/plugins.thunks'; +import { getProjectById } from '../project/project.thunks'; +import { setPerfectMatch } from '../search/search.slice'; +import { getSearchData } from '../search/search.thunks'; import { getStatisticsById } from '../statistics/statistics.thunks'; +import { getSessionValid } from '../user/user.thunks'; interface InitializeAppParams { queryData: QueryData; @@ -54,6 +55,9 @@ export const fetchInitialAppData = createAsyncThunk< dispatch(getStatisticsById(PROJECT_ID)); dispatch(getConfiguration()); + // Fetch plugins list + dispatch(getAllPlugins()); + /** Trigger search */ if (queryData.searchValue) { dispatch(setPerfectMatch(queryData.perfectMatch)); diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts index c236df284e59fbdba9e80a306213f18410e9cc67..8bfe3f1bcafc071a633a9da208708244fb101015 100644 --- a/src/redux/root/root.fixtures.ts +++ b/src/redux/root/root.fixtures.ts @@ -1,25 +1,26 @@ 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 { COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK } from '../compartmentPathways/compartmentPathways.mock'; import { CONFIGURATION_INITIAL_STATE } from '../configuration/configuration.adapter'; import { CONTEXT_MENU_INITIAL_STATE } from '../contextMenu/contextMenu.constants'; import { COOKIE_BANNER_INITIAL_STATE_MOCK } from '../cookieBanner/cookieBanner.mock'; import { initialStateFixture as drawerInitialStateMock } from '../drawer/drawerFixture'; import { DRUGS_INITIAL_STATE_MOCK } from '../drugs/drugs.mock'; +import { EXPORT_INITIAL_STATE_MOCK } from '../export/export.mock'; import { LEGEND_INITIAL_STATE_MOCK } from '../legend/legend.mock'; import { initialMapStateFixture } from '../map/map.fixtures'; import { MODAL_INITIAL_STATE_MOCK } from '../modal/modal.mock'; import { MODELS_INITIAL_STATE_MOCK } from '../models/models.mock'; import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '../overlayBioEntity/overlayBioEntity.mock'; import { OVERLAYS_INITIAL_STATE_MOCK } from '../overlays/overlays.mock'; +import { PLUGINS_INITIAL_STATE_MOCK } from '../plugins/plugins.mock'; import { PROJECT_STATE_INITIAL_MOCK } from '../project/project.mock'; import { REACTIONS_STATE_INITIAL_MOCK } from '../reactions/reactions.mock'; import { SEARCH_STATE_INITIAL_MOCK } from '../search/search.mock'; +import { STATISTICS_STATE_INITIAL_MOCK } from '../statistics/statistics.mock'; import { RootState } from '../store'; import { USER_INITIAL_STATE_MOCK } from '../user/user.mock'; -import { STATISTICS_STATE_INITIAL_MOCK } from '../statistics/statistics.mock'; -import { COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK } from '../compartmentPathways/compartmentPathways.mock'; -import { EXPORT_INITIAL_STATE_MOCK } from '../export/export.mock'; export const INITIAL_STORE_STATE_MOCK: RootState = { search: SEARCH_STATE_INITIAL_MOCK, @@ -43,4 +44,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = { statistics: STATISTICS_STATE_INITIAL_MOCK, compartmentPathways: COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK, export: EXPORT_INITIAL_STATE_MOCK, + plugins: PLUGINS_INITIAL_STATE_MOCK, }; diff --git a/src/redux/store.ts b/src/redux/store.ts index a945a7518a65a15740f4922027fc4eb864b3d1cd..4e7447eba76d7e6d33cb5f70ffc8fc9b096ade8a 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -22,11 +22,12 @@ import { TypedStartListening, configureStore, } from '@reduxjs/toolkit'; +import compartmentPathwaysReducer from './compartmentPathways/compartmentPathways.slice'; +import exportReducer from './export/export.slice'; import legendReducer from './legend/legend.slice'; import { mapListenerMiddleware } from './map/middleware/map.middleware'; +import pluginsReducer from './plugins/plugins.slice'; import statisticsReducer from './statistics/statistics.slice'; -import compartmentPathwaysReducer from './compartmentPathways/compartmentPathways.slice'; -import exportReducer from './export/export.slice'; export const reducers = { search: searchReducer, @@ -50,6 +51,7 @@ export const reducers = { statistics: statisticsReducer, compartmentPathways: compartmentPathwaysReducer, export: exportReducer, + plugins: pluginsReducer, }; export const middlewares = [mapListenerMiddleware.middleware]; diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index a5f3e3d2ff2f0155c67bdb1a458fc2ce64c5764f..3715c57ddd45995b4c8194a917bf0ad8a129b5f4 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -8,4 +8,5 @@ export type DrawerName = | 'submaps' | 'reaction' | 'overlays' - | 'bio-entity'; + | 'bio-entity' + | 'available-plugins'; diff --git a/src/types/models.ts b/src/types/models.ts index 437fa8d6655cbdc71ea1d9ec155d965a20fed0fa..293fd8ffe08e9930c8d2b88385a697b16834106e 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -37,6 +37,7 @@ import { overviewImageLinkModel, } from '@/models/overviewImageLink'; import { overviewImageView } from '@/models/overviewImageView'; +import { pluginSchema } from '@/models/pluginSchema'; import { projectSchema } from '@/models/projectSchema'; import { reactionSchema } from '@/models/reaction'; import { reactionLineSchema } from '@/models/reactionLineSchema'; @@ -88,3 +89,4 @@ export type CompartmentPathway = z.infer<typeof compartmentPathwaySchema>; export type CompartmentPathwayDetails = z.infer<typeof compartmentPathwayDetailsSchema>; export type ExportNetwork = z.infer<typeof exportNetworkchema>; export type ExportElements = z.infer<typeof exportElementsSchema>; +export type MinervaPlugin = z.infer<typeof pluginSchema>; // Plugin type interfers with global Plugin type