feat: create Studio Home Page MFE (#589)
This commit is contained in:
44
src/generic/data/api.js
Normal file
44
src/generic/data/api.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { convertObjectToSnakeCase } from '../../utils';
|
||||
|
||||
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
export const getCreateOrRerunCourseUrl = new URL('course/', getApiBaseUrl()).href;
|
||||
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
||||
export const getOrganizationsUrl = new URL('organizations', getApiBaseUrl()).href;
|
||||
|
||||
/**
|
||||
* Get's organizations data.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getOrganizations() {
|
||||
const { data } = await getAuthenticatedHttpClient().get(
|
||||
getOrganizationsUrl,
|
||||
);
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's course rerun data.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseRerun(courseId) {
|
||||
const { data } = await getAuthenticatedHttpClient().get(
|
||||
getCourseRerunUrl(courseId),
|
||||
);
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or rerun course with data.
|
||||
* @param {object} data
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function createOrRerunCourse(courseData) {
|
||||
const { data } = await getAuthenticatedHttpClient().post(
|
||||
getCreateOrRerunCourseUrl,
|
||||
convertObjectToSnakeCase(courseData, true),
|
||||
);
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
75
src/generic/data/api.test.js
Normal file
75
src/generic/data/api.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import {
|
||||
createOrRerunCourse,
|
||||
getApiBaseUrl,
|
||||
getOrganizations,
|
||||
getCreateOrRerunCourseUrl,
|
||||
getCourseRerunUrl,
|
||||
getCourseRerun,
|
||||
} from './api';
|
||||
|
||||
let axiosMock;
|
||||
|
||||
describe('generic api calls', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get organizations', async () => {
|
||||
const organizationsData = ['edX', 'org'];
|
||||
const queryUrl = new URL('organizations', getApiBaseUrl()).href;
|
||||
axiosMock.onGet(queryUrl).reply(200, organizationsData);
|
||||
const result = await getOrganizations();
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(queryUrl);
|
||||
expect(result).toEqual(organizationsData);
|
||||
});
|
||||
|
||||
it('should get course rerun', async () => {
|
||||
const courseId = 'course-mock-id';
|
||||
const courseRerunData = {
|
||||
allowUnicodeCourseId: false,
|
||||
courseCreatorStatus: 'granted',
|
||||
displayName: 'Demonstration Course',
|
||||
number: 'DemoX',
|
||||
org: 'edX',
|
||||
run: 'Demo_Course',
|
||||
};
|
||||
axiosMock.onGet(getCourseRerunUrl(courseId)).reply(200, courseRerunData);
|
||||
const result = await getCourseRerun(courseId);
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(getCourseRerunUrl(courseId));
|
||||
expect(result).toEqual(courseRerunData);
|
||||
});
|
||||
|
||||
it('should post create or rerun course', async () => {
|
||||
const courseRerunData = {
|
||||
allowUnicodeCourseId: false,
|
||||
courseCreatorStatus: 'granted',
|
||||
displayName: 'Demonstration Course',
|
||||
number: 'DemoX',
|
||||
org: 'edX',
|
||||
run: 'Demo_Course',
|
||||
};
|
||||
axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, courseRerunData);
|
||||
const result = await createOrRerunCourse(courseRerunData);
|
||||
|
||||
expect(axiosMock.history.post[0].url).toEqual(getCreateOrRerunCourseUrl);
|
||||
expect(result).toEqual(courseRerunData);
|
||||
});
|
||||
});
|
||||
7
src/generic/data/selectors.js
Normal file
7
src/generic/data/selectors.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const getLoadingStatuses = (state) => state.generic.loadingStatuses;
|
||||
export const getSavingStatus = (state) => state.generic.savingStatus;
|
||||
export const getOrganizations = (state) => state.generic.organizations;
|
||||
export const getCourseData = (state) => state.generic.createOrRerunCourse.courseData;
|
||||
export const getCourseRerunData = (state) => state.generic.createOrRerunCourse.courseRerunData;
|
||||
export const getRedirectUrlObj = (state) => state.generic.createOrRerunCourse.redirectUrlObj;
|
||||
export const getPostErrors = (state) => state.generic.createOrRerunCourse.postErrors;
|
||||
59
src/generic/data/slice.js
Normal file
59
src/generic/data/slice.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'generic',
|
||||
initialState: {
|
||||
loadingStatuses: {
|
||||
organizationLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseRerunLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
},
|
||||
savingStatus: '',
|
||||
organizations: [],
|
||||
createOrRerunCourse: {
|
||||
courseData: {},
|
||||
courseRerunData: {},
|
||||
redirectUrlObj: {},
|
||||
postErrors: {},
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
fetchOrganizations: (state, { payload }) => {
|
||||
state.organizations = payload;
|
||||
},
|
||||
updateLoadingStatuses: (state, { payload }) => {
|
||||
state.loadingStatuses = { ...state.loadingStatuses, ...payload };
|
||||
},
|
||||
updateSavingStatus: (state, { payload }) => {
|
||||
state.savingStatus = payload.status;
|
||||
},
|
||||
updateCourseData: (state, { payload }) => {
|
||||
state.createOrRerunCourse.courseData = payload;
|
||||
},
|
||||
updateCourseRerunData: (state, { payload }) => {
|
||||
state.createOrRerunCourse.courseRerunData = payload;
|
||||
},
|
||||
updateRedirectUrlObj: (state, { payload }) => {
|
||||
state.createOrRerunCourse.redirectUrlObj = payload;
|
||||
},
|
||||
updatePostErrors: (state, { payload }) => {
|
||||
state.createOrRerunCourse.postErrors = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
fetchOrganizations,
|
||||
updatePostErrors,
|
||||
updateCourseRerunData,
|
||||
updateLoadingStatuses,
|
||||
updateSavingStatus,
|
||||
updateCourseData,
|
||||
updateRedirectUrlObj,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
reducer,
|
||||
} = slice;
|
||||
51
src/generic/data/thunks.js
Normal file
51
src/generic/data/thunks.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { createOrRerunCourse, getOrganizations, getCourseRerun } from './api';
|
||||
import {
|
||||
fetchOrganizations,
|
||||
updatePostErrors,
|
||||
updateLoadingStatuses,
|
||||
updateRedirectUrlObj,
|
||||
updateCourseRerunData,
|
||||
updateSavingStatus,
|
||||
} from './slice';
|
||||
|
||||
export function fetchOrganizationsQuery() {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const organizations = await getOrganizations();
|
||||
dispatch(fetchOrganizations(organizations));
|
||||
dispatch(updateLoadingStatuses({ organizationLoadingStatus: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
dispatch(updateLoadingStatuses({ organizationLoadingStatus: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCourseRerunQuery(courseId) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const courseRerun = await getCourseRerun(courseId);
|
||||
dispatch(updateCourseRerunData(courseRerun));
|
||||
dispatch(updateLoadingStatuses({ courseRerunLoadingStatus: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
dispatch(updateLoadingStatuses({ courseRerunLoadingStatus: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function updateCreateOrRerunCourseQuery(courseData) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
|
||||
try {
|
||||
const response = await createOrRerunCourse(courseData);
|
||||
dispatch(updateRedirectUrlObj('url' in response ? response : {}));
|
||||
dispatch(updatePostErrors('errMsg' in response ? response : {}));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user