Skip to content
Snippets Groups Projects
Commit 3c109b0f authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '253-min-320-opening-project-without-permission' into 'development'

Resolve "[MIN-320] opening project without permission"

Closes #253

See merge request !204
parents 37250688 f8746b1b
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...,!204Resolve "[MIN-320] opening project without permission"
Pipeline #92322 passed
Showing
with 277 additions and 2 deletions
import { OAuth } from '@/types/models';
export const initialOAuthFixture: OAuth = {
Orcid: undefined,
};
import { DEFAULT_ERROR } from '@/constants/errors';
import { OauthState } from '@/redux/oauth/oauth.types';
import { initialOAuthFixture } from '@/redux/oauth/oauth.fixtures';
export const OAUTH_INITIAL_STATE_MOCK: OauthState = {
data: initialOAuthFixture,
loading: 'idle',
error: DEFAULT_ERROR,
orcidEndpoint: undefined,
};
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { OauthState } from '@/redux/oauth/oauth.types';
import { getOAuth } from '@/redux/oauth/oauth.thunks';
export const getOauthReducer = (builder: ActionReducerMapBuilder<OauthState>): void => {
builder.addCase(getOAuth.pending, state => {
state.loading = 'pending';
});
builder.addCase(getOAuth.fulfilled, (state, action) => {
state.data = action.payload || undefined;
if (action.payload && action.payload.Orcid) {
state.orcidEndpoint = action.payload.Orcid;
}
state.loading = 'succeeded';
});
builder.addCase(getOAuth.rejected, state => {
state.loading = 'failed';
// TODO to discuss manage state of failure
});
};
import { createSelector } from '@reduxjs/toolkit';
import { rootSelector } from '../root/root.selectors';
export const oauthSelector = createSelector(rootSelector, state => state.oauth);
export const orcidEndpointSelector = createSelector(oauthSelector, oauth => oauth?.orcidEndpoint);
import { createSlice } from '@reduxjs/toolkit';
import { OauthState } from '@/redux/oauth/oauth.types';
import { getOauthReducer } from '@/redux/oauth/oauth.reducers';
const initialState: OauthState = {
data: undefined,
loading: 'idle',
error: { name: '', message: '' },
orcidEndpoint: undefined,
};
const oauthSlice = createSlice({
name: 'oauth',
initialState,
reducers: {},
extraReducers: builder => {
getOauthReducer(builder);
},
});
export default oauthSlice.reducer;
import { axiosInstance } from '@/services/api/utils/axiosInstance';
import { OAuth } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { ThunkConfig } from '@/types/store';
import { getError } from '@/utils/error-report/getError';
import { oauthSchema } from '@/models/oauthSchema';
import { OAUTH_FETCHING_ERROR_PREFIX } from '@/redux/oauth/oauth.constants';
import { apiPath } from '../apiPath';
export const getOAuth = createAsyncThunk<OAuth | undefined, void, ThunkConfig>(
'oauth/getProviders',
async () => {
try {
const response = await axiosInstance.get<OAuth>(apiPath.getOauthProviders());
const isDataValid = validateDataUsingZodSchema(response.data, oauthSchema);
return isDataValid ? response.data : undefined;
} catch (error) {
return Promise.reject(getError({ error, prefix: OAUTH_FETCHING_ERROR_PREFIX }));
}
},
);
import { OAuth } from '@/types/models';
import { Loading } from '@/types/loadingState';
export type OauthState = {
orcidEndpoint: string | undefined;
data: OAuth | undefined;
loading: Loading;
error: Error;
};
export const PROJECTS_FETCHING_ERROR_PREFIX = 'Failed to fetch projects';
import { DEFAULT_ERROR } from '@/constants/errors';
import { ProjectsState } from '@/redux/projects/projects.types';
export const PROJECTS_STATE_INITIAL_MOCK: ProjectsState = {
data: [],
loading: 'idle',
error: DEFAULT_ERROR,
};
import { projectPageFixture } from '@/models/fixtures/projectsFixture';
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import { unwrapResult } from '@reduxjs/toolkit';
import { ProjectsState } from '@/redux/projects/projects.types';
import { getProjects } from '@/redux/projects/projects.thunks';
import { apiPath } from '../apiPath';
import projectsReducer from './projects.slice';
const mockedAxiosClient = mockNetworkNewAPIResponse();
const INITIAL_STATE: ProjectsState = {
data: [],
loading: 'idle',
error: { name: '', message: '' },
};
describe('projects reducer', () => {
let store = {} as ToolkitStoreWithSingleSlice<ProjectsState>;
beforeEach(() => {
store = createStoreInstanceUsingSliceReducer('projects', projectsReducer);
});
it('should match initial state', () => {
const action = { type: 'unknown' };
expect(projectsReducer(undefined, action)).toEqual(INITIAL_STATE);
});
it('should update store after successfull getProjects query', async () => {
mockedAxiosClient.onGet(apiPath.getProjects()).reply(HttpStatusCode.Ok, projectPageFixture);
const { type } = await store.dispatch(getProjects());
const { data, loading, error } = store.getState().projects;
expect(type).toBe('project/getProjects/fulfilled');
expect(loading).toEqual('succeeded');
expect(error).toEqual({ message: '', name: '' });
expect(data).toEqual(projectPageFixture.content);
});
it('should update store after failed getProjects query', async () => {
mockedAxiosClient.onGet(apiPath.getProjects()).reply(HttpStatusCode.NotFound, []);
const action = await store.dispatch(getProjects());
const { data, loading, error } = store.getState().projects;
expect(action.type).toBe('project/getProjects/rejected');
expect(() => unwrapResult(action)).toThrow(
"Failed to fetch projects: The page you're looking for doesn't exist. Please verify the URL and try again.",
);
expect(loading).toEqual('failed');
expect(error).toEqual({ message: '', name: '' });
expect(data).toEqual([]);
});
it('should update store on loading getProjects query', async () => {
mockedAxiosClient.onGet(apiPath.getProjects()).reply(HttpStatusCode.Ok, projectPageFixture);
const actionPromise = store.dispatch(getProjects());
const { data, loading } = store.getState().projects;
expect(data).toEqual([]);
expect(loading).toEqual('pending');
actionPromise.then(() => {
const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().projects;
expect(dataPromiseFulfilled).toEqual(projectPageFixture.content);
expect(promiseFulfilled).toEqual('succeeded');
});
});
});
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { ProjectsState } from '@/redux/projects/projects.types';
import { getProjects } from '@/redux/projects/projects.thunks';
export const getProjectsReducer = (builder: ActionReducerMapBuilder<ProjectsState>): void => {
builder.addCase(getProjects.pending, state => {
state.loading = 'pending';
});
builder.addCase(getProjects.fulfilled, (state, action) => {
state.data = action.payload || undefined;
state.loading = 'succeeded';
});
builder.addCase(getProjects.rejected, state => {
state.loading = 'failed';
// TODO to discuss manage state of failure
});
};
import { rootSelector } from '@/redux/root/root.selectors';
import { createSelector } from '@reduxjs/toolkit';
export const projectsRootSelector = createSelector(rootSelector, state => state.projects);
export const projectsSelector = createSelector(projectsRootSelector, state => state.data);
import { createSlice } from '@reduxjs/toolkit';
import { ProjectsState } from '@/redux/projects/projects.types';
import { getProjectsReducer } from '@/redux/projects/projects.reducers';
const initialState: ProjectsState = {
data: [],
loading: 'idle',
error: { name: '', message: '' },
};
const projectsSlice = createSlice({
name: 'project',
initialState,
reducers: {},
extraReducers: builder => {
getProjectsReducer(builder);
},
});
export default projectsSlice.reducer;
import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
import { projectSchema } from '@/models/projectSchema';
import { PageOf, Project } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { ThunkConfig } from '@/types/store';
import { getError } from '@/utils/error-report/getError';
import { PROJECTS_FETCHING_ERROR_PREFIX } from '@/redux/projects/projects.constants';
import { pageableSchema } from '@/models/pageableSchema';
import { apiPath } from '../apiPath';
export const getProjects = createAsyncThunk<Project[], void, ThunkConfig>(
'project/getProjects',
async () => {
try {
const response = await axiosInstanceNewAPI.get<PageOf<Project>>(apiPath.getProjects());
const isDataValid = validateDataUsingZodSchema(response.data, pageableSchema(projectSchema));
return isDataValid ? response.data.content : [];
} catch (error) {
return Promise.reject(getError({ error, prefix: PROJECTS_FETCHING_ERROR_PREFIX }));
}
},
);
import { Project } from '@/types/models';
import { FetchDataState } from '@/types/fetchDataState';
export type ProjectsState = FetchDataState<Project[], []>;
import { CONSTANT_INITIAL_STATE } from '@/redux/constant/constant.adapter';
import { PROJECTS_STATE_INITIAL_MOCK } from '@/redux/projects/projects.mock';
import { OAUTH_INITIAL_STATE_MOCK } from '@/redux/oauth/oauth.mock';
import { COMMENT_INITIAL_STATE_MOCK } from '@/redux/comment/comment.mock';
import { BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock';
import { BIOENTITY_INITIAL_STATE_MOCK } from '../bioEntity/bioEntity.mock';
......@@ -30,6 +32,7 @@ import { USER_INITIAL_STATE_MOCK } from '../user/user.mock';
export const INITIAL_STORE_STATE_MOCK: RootState = {
search: SEARCH_STATE_INITIAL_MOCK,
project: PROJECT_STATE_INITIAL_MOCK,
projects: PROJECTS_STATE_INITIAL_MOCK,
drugs: DRUGS_INITIAL_STATE_MOCK,
chemicals: CHEMICALS_INITIAL_STATE_MOCK,
models: MODELS_INITIAL_STATE_MOCK,
......@@ -37,6 +40,7 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
drawer: drawerInitialStateMock,
map: initialMapStateFixture,
oauth: OAUTH_INITIAL_STATE_MOCK,
overlays: OVERLAYS_INITIAL_STATE_MOCK,
reactions: REACTIONS_STATE_INITIAL_MOCK,
configuration: CONFIGURATION_INITIAL_STATE,
......
......@@ -10,9 +10,11 @@ import drugsReducer from '@/redux/drugs/drugs.slice';
import mapReducer from '@/redux/map/map.slice';
import modalReducer from '@/redux/modal/modal.slice';
import modelsReducer from '@/redux/models/models.slice';
import oauthReducer from '@/redux/oauth/oauth.slice';
import overlayBioEntityReducer from '@/redux/overlayBioEntity/overlayBioEntity.slice';
import overlaysReducer from '@/redux/overlays/overlays.slice';
import projectReducer from '@/redux/project/project.slice';
import projectsReducer from '@/redux/projects/projects.slice';
import reactionsReducer from '@/redux/reactions/reactions.slice';
import searchReducer from '@/redux/search/search.slice';
import userReducer from '@/redux/user/user.slice';
......@@ -38,6 +40,7 @@ import statisticsReducer from './statistics/statistics.slice';
export const reducers = {
search: searchReducer,
project: projectReducer,
projects: projectsReducer,
drugs: drugsReducer,
chemicals: chemicalsReducer,
bioEntity: bioEntityReducer,
......@@ -63,6 +66,7 @@ export const reducers = {
plugins: pluginsReducer,
markers: markersReducer,
entityNumber: entityNumberReducer,
oauth: oauthReducer,
};
export const middlewares = [mapListenerMiddleware.middleware, errorListenerMiddleware.middleware];
......
......@@ -7,4 +7,5 @@ export type ModalName =
| 'publications'
| 'edit-overlay'
| 'error-report'
| 'access-denied'
| 'logged-in-menu';
......@@ -64,6 +64,7 @@ import { z } from 'zod';
import { commentSchema } from '@/models/commentSchema';
import { userSchema } from '@/models/userSchema';
import { javaStacktraceSchema } from '@/models/javaStacktraceSchema';
import { oauthSchema } from '@/models/oauthSchema';
export type Project = z.infer<typeof projectSchema>;
export type OverviewImageView = z.infer<typeof overviewImageView>;
......@@ -124,3 +125,14 @@ export type MarkerWithPosition = z.infer<typeof markerWithPositionSchema>;
export type Marker = z.infer<typeof markerSchema>;
export type JavaStacktrace = z.infer<typeof javaStacktraceSchema>;
export type Comment = z.infer<typeof commentSchema>;
export type PageOf<T> = {
totalPages: number;
totalElements: number;
numberOfElements: number;
size: number;
number: number;
content: T[];
};
export type OAuth = z.infer<typeof oauthSchema>;
......@@ -6,9 +6,11 @@ export const validateDataUsingZodSchema: IsApiResponseValid = (data, schema: Zod
const validationResults = schema.safeParse(data);
if (validationResults.success === false) {
// TODO - probably need to rething way of handling parsing errors, for now let's leave it to console.log
// TODO - probably need to rethink way of handling parsing errors, for now let's leave it to console.log
// eslint-disable-next-line no-console
console.error('Error on parsing data', validationResults.error.message);
console.error('Error on parsing data', validationResults.error);
// eslint-disable-next-line no-console
console.error(validationResults.error.message);
}
return validationResults.success;
......
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