Skip to content
Snippets Groups Projects
Commit 1bfc48af authored by mateusz-winiarczyk's avatar mateusz-winiarczyk
Browse files

Merge branch 'MIN-226-plugins-submaps' into 'development'

feat(submaps): Plugins Submaps (MIN-226)

See merge request !137
parents e4fd45c4 6309e0e1
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!137feat(submaps): Plugins Submaps (MIN-226)
Pipeline #86925 passed
Showing
with 265 additions and 8 deletions
# Error Documentation
## Map Errors
- **Map with provided id does not exist**: This error occurs when the provided map id does not correspond to any existing map.
## Search Errors
- **Invalid query type. The query should be of string type**: This error occurs when the query parameter is not of string type.
- **Invalid coordinates type or values**: This error occurs when the coordinates parameter is missing keys, or its values are not of number type.
- **Invalid model id type. The model should be of number type**: This error occurs when the modelId parameter is not of number type.
## Project Errors
- **Project does not exist**: This error occurs when the project data is not available.
### Submaps
#### Get Models
To get data about all available submaps, plugins can use the `getModels` method defined in `window.minerva.map.data`. This method returns array with data about all submaps.
##### Example of getModels usage:
```javascript
window.minerva.map.data.getModels();
```
#### Open Map
To open map, plugins can use the `openMap` method defined in `window.minerva.map`. This method takes one argument: an object with an `id` property that indicates the map ID.
##### Example of openMap usage:
```javascript
window.minerva.map.openMap({ id: 51 });
```
import { getModels } from '@/services/pluginsManager/map/models/getModels';
import { OpenMapArgs, openMap } from '@/services/pluginsManager/map/openMap';
import { triggerSearch } from '@/services/pluginsManager/map/triggerSearch';
import { MinervaConfiguration } from '@/services/pluginsManager/pluginsManager';
import { MapModel } from '@/types/models';
import { getDisease } from '@/services/pluginsManager/project/data/getDisease';
import { getName } from '@/services/pluginsManager/project/data/getName';
import { getOrganism } from '@/services/pluginsManager/project/data/getOrganism';
......@@ -32,6 +35,10 @@ declare global {
bioEntities: BioEntitiesMethods;
};
map: {
data: {
getModels: typeof getModels;
};
openMap: typeof openMap;
triggerSearch: typeof triggerSearch;
};
project: {
......
export const ERROR_MAP_NOT_FOUND = 'Map with provided id does not exist';
export const ERROR_INVALID_QUERY_TYPE = 'Invalid query type. The query should be of string type';
export const ERROR_INVALID_COORDINATES = 'Invalid coordinates type or values';
export const ERROR_INVALID_MODEL_ID_TYPE =
'Invalid model id type. The model should be of number type';
export const ERROR_PROJECT_NOT_FOUND = 'Project does not exist';
import { RootState, store } from '@/redux/store';
import { modelsFixture } from '@/models/fixtures/modelsFixture';
import { getModels } from './getModels';
jest.mock('../../../../redux/store');
describe('getModels', () => {
const getStateSpy = jest.spyOn(store, 'getState');
beforeEach(() => {
jest.clearAllMocks();
});
it('should return models when data is valid', () => {
getStateSpy.mockImplementation(
() =>
({
models: {
data: modelsFixture,
},
}) as RootState,
);
expect(getModels()).toEqual(modelsFixture);
});
it('should return empty array when data is invalid', () => {
getStateSpy.mockImplementation(
() =>
({
models: {
data: 'invalid',
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
);
expect(getModels()).toEqual([]);
});
it('should return empty array when data is empty', () => {
getStateSpy.mockImplementation(
() =>
({
models: {
data: [],
},
}) as RootState,
);
expect(getModels()).toEqual([]);
});
});
import { mapModelSchema } from '@/models/modelSchema';
import { store } from '@/redux/store';
import { MapModel } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { z } from 'zod';
export const getModels = (): MapModel[] => {
const models = store.getState().models.data;
const isDataValid = validateDataUsingZodSchema(models, z.array(mapModelSchema));
return isDataValid ? models : [];
};
/* eslint-disable no-magic-numbers */
import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice';
import { RootState, store } from '@/redux/store';
import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
import { MODELS_MOCK, MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
import { PluginsEventBus } from '../pluginsEventBus';
import { openMap } from './openMap';
jest.mock('../../../redux/store');
describe('openMap', () => {
const getStateSpy = jest.spyOn(store, 'getState');
const dispatchSpy = jest.spyOn(store, 'dispatch');
const pluginDispatchEvent = jest.spyOn(PluginsEventBus, 'dispatchEvent');
beforeEach(() => {
jest.clearAllMocks();
});
it('should set active map when map with provided id is already opened', () => {
getStateSpy.mockImplementation(
() =>
({
map: {
data: { ...initialMapDataFixture, modelId: 5052 },
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
models: {
data: MODELS_MOCK_SHORT,
loading: 'succeeded',
error: { name: '', message: '' },
},
}) as RootState,
);
openMap({ id: 5053 });
expect(dispatchSpy).toHaveBeenCalledWith(setActiveMap({ modelId: 5053 }));
expect(pluginDispatchEvent).toHaveBeenCalledWith('onSubmapClose', 5052);
expect(pluginDispatchEvent).toHaveBeenCalledWith('onSubmapOpen', 5053);
});
it('should open map and set active map when map with provided id is not already opened', () => {
getStateSpy.mockImplementation(
() =>
({
map: {
data: { ...initialMapDataFixture, modelId: 5052 },
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
models: {
data: MODELS_MOCK,
loading: 'succeeded',
error: { name: '', message: '' },
},
}) as RootState,
);
openMap({ id: 5061 });
expect(dispatchSpy).toHaveBeenCalledWith(
openMapAndSetActive({ modelId: 5061, modelName: 'Wnt signaling' }),
);
expect(pluginDispatchEvent).toHaveBeenCalledWith('onSubmapClose', 5052);
expect(pluginDispatchEvent).toHaveBeenCalledWith('onSubmapOpen', 5061);
});
it('should throw an error when map with provided id does not exist', () => {
getStateSpy.mockImplementation(
() =>
({
map: {
data: { ...initialMapDataFixture, modelId: 5052 },
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
models: {
data: MODELS_MOCK,
loading: 'succeeded',
error: { name: '', message: '' },
},
}) as RootState,
);
expect(() => openMap({ id: 3 })).toThrow('Map with provided id does not exist');
expect(store.dispatch).not.toHaveBeenCalled();
expect(PluginsEventBus.dispatchEvent).not.toHaveBeenCalled();
});
});
import { mapModelIdSelector, mapOpenedMapsSelector } from '@/redux/map/map.selectors';
import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice';
import { modelsDataSelector } from '@/redux/models/models.selectors';
import { store } from '@/redux/store';
import { PluginsEventBus } from '../pluginsEventBus';
import { ERROR_MAP_NOT_FOUND } from '../errorMessages';
export type OpenMapArgs = {
id: number;
};
export const openMap = ({ id }: OpenMapArgs): void => {
const { getState, dispatch } = store;
const models = modelsDataSelector(getState());
const openedMaps = mapOpenedMapsSelector(getState());
const mapToOpen = models.find(model => model.idObject === id);
const currentModelId = mapModelIdSelector(getState());
if (!mapToOpen) throw new Error(ERROR_MAP_NOT_FOUND);
const isMapAlreadyOpened = openedMaps.some(map => map.modelId === mapToOpen.idObject);
if (isMapAlreadyOpened) {
dispatch(setActiveMap({ modelId: mapToOpen.idObject }));
} else {
dispatch(openMapAndSetActive({ modelId: mapToOpen.idObject, modelName: mapToOpen.name }));
}
if (currentModelId !== mapToOpen.idObject) {
PluginsEventBus.dispatchEvent('onSubmapClose', currentModelId);
PluginsEventBus.dispatchEvent('onSubmapOpen', mapToOpen.idObject);
}
};
import { SearchParams } from './triggerSearch.types';
import { searchByQuery } from './searchByQuery';
import { searchByCoordinates } from './searchByCoordinates';
import {
ERROR_INVALID_COORDINATES,
ERROR_INVALID_MODEL_ID_TYPE,
ERROR_INVALID_QUERY_TYPE,
} from '../../errorMessages';
export async function triggerSearch(params: SearchParams): Promise<void> {
if ('query' in params) {
if (typeof params.query !== 'string') {
throw new Error('Invalid query type. The query should be of string type');
throw new Error(ERROR_INVALID_QUERY_TYPE);
}
searchByQuery(params.query, params.perfectSearch);
} else {
......@@ -16,11 +21,11 @@ export async function triggerSearch(params: SearchParams): Promise<void> {
typeof params.coordinates.x !== 'number' || typeof params.coordinates.y !== 'number';
if (areCoordinatesInvalidType || areCoordinatesMissingKeys || areCoordinatesValuesInvalid) {
throw new Error('Invalid coordinates type or values');
throw new Error(ERROR_INVALID_COORDINATES);
}
if (typeof params.modelId !== 'number') {
throw new Error('Invalid model id type. The model should be of number type');
throw new Error(ERROR_INVALID_MODEL_ID_TYPE);
}
searchByCoordinates(params.coordinates, params.modelId);
......
......@@ -2,6 +2,8 @@ import { PLUGINS_CONTENT_ELEMENT_ATTR_NAME, PLUGINS_CONTENT_ELEMENT_ID } from '@
import { registerPlugin } from '@/redux/plugins/plugins.thunks';
import { store } from '@/redux/store';
import md5 from 'crypto-js/md5';
import { getModels } from './map/models/getModels';
import { openMap } from './map/openMap';
import { bioEntitiesMethods } from './bioEntities';
import { triggerSearch } from './map/triggerSearch';
import { PluginsEventBus } from './pluginsEventBus';
......@@ -31,6 +33,10 @@ export const PluginsManager: PluginsManagerType = {
bioEntities: bioEntitiesMethods,
},
map: {
data: {
getModels,
},
openMap,
triggerSearch,
},
project: {
......
......@@ -2,13 +2,14 @@ import { projectSchema } from '@/models/projectSchema';
import { store } from '@/redux/store';
import { Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { ERROR_PROJECT_NOT_FOUND } from '../../errorMessages';
type GetDiseaseReturnType = Project['disease'] | undefined;
export const getDisease = (): GetDiseaseReturnType => {
const project = store.getState().project.data;
if (!project) throw new Error('Project does not exist');
if (!project) throw new Error(ERROR_PROJECT_NOT_FOUND);
const isDataValid = validateDataUsingZodSchema(project, projectSchema);
......
......@@ -2,13 +2,14 @@ import { projectSchema } from '@/models/projectSchema';
import { store } from '@/redux/store';
import { Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { ERROR_PROJECT_NOT_FOUND } from '../../errorMessages';
type GetNameReturnType = Project['name'] | undefined;
export const getName = (): GetNameReturnType => {
const project = store.getState().project.data;
if (!project) throw new Error('Project does not exist');
if (!project) throw new Error(ERROR_PROJECT_NOT_FOUND);
const isDataValid = validateDataUsingZodSchema(project, projectSchema);
......
......@@ -2,13 +2,14 @@ import { projectSchema } from '@/models/projectSchema';
import { store } from '@/redux/store';
import { Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { ERROR_PROJECT_NOT_FOUND } from '../../errorMessages';
type GetOrganismReturnType = Project['organism'] | undefined;
export const getOrganism = (): GetOrganismReturnType => {
const project = store.getState().project.data;
if (!project) throw new Error('Project does not exist');
if (!project) throw new Error(ERROR_PROJECT_NOT_FOUND);
const isDataValid = validateDataUsingZodSchema(project, projectSchema);
......
......@@ -2,13 +2,14 @@ import { projectSchema } from '@/models/projectSchema';
import { store } from '@/redux/store';
import { Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { ERROR_PROJECT_NOT_FOUND } from '../../errorMessages';
type GetProjectIdReturnType = Project['projectId'] | undefined;
export const getProjectId = (): GetProjectIdReturnType => {
const project = store.getState().project.data;
if (!project) throw new Error('Project does not exist');
if (!project) throw new Error(ERROR_PROJECT_NOT_FOUND);
const isDataValid = validateDataUsingZodSchema(project, projectSchema);
......
......@@ -2,13 +2,14 @@ import { projectSchema } from '@/models/projectSchema';
import { store } from '@/redux/store';
import { Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { ERROR_PROJECT_NOT_FOUND } from '../../errorMessages';
type GetVersionReturnType = Project['version'] | undefined;
export const getVersion = (): GetVersionReturnType => {
const project = store.getState().project.data;
if (!project) throw new Error('Project does not exist');
if (!project) throw new Error(ERROR_PROJECT_NOT_FOUND);
const isDataValid = validateDataUsingZodSchema(project, projectSchema);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment