feat: create Studio Home Page MFE (#589)

This commit is contained in:
ruzniaievdm
2023-09-19 17:04:43 +03:00
committed by GitHub
parent 6f238bdbe0
commit e099243437
107 changed files with 5976 additions and 309 deletions

44
src/generic/data/api.js Normal file
View 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);
}

View 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);
});
});

View 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
View 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;

View 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;
}
};
}