feat: Added permission alert in pages (#153)

* feat: Added permission alert on pages
This commit is contained in:
Ahtisham Shahid
2021-07-13 10:53:48 +05:00
committed by GitHub
parent e23e8e8957
commit c4215d9cee
6 changed files with 103 additions and 12 deletions

View File

@@ -1,11 +1,15 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Footer from '@edx/frontend-component-footer';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import Header from './studio-header/Header';
import { fetchCourseDetail } from './data/thunks';
import { useModel } from './generic/model-store';
import PermissionDeniedAlert from './generic/PermissionDeniedAlert';
import { getCourseAppsApiStatus, getLoadingStatus } from './pages-and-resources/data/selectors';
import { RequestStatus } from './data/constants';
import Loading from './generic/Loading';
export default function CourseAuthoringPage({ courseId, children }) {
const dispatch = useDispatch();
@@ -19,17 +23,28 @@ export default function CourseAuthoringPage({ courseId, children }) {
const courseNumber = courseDetail ? courseDetail.number : null;
const courseOrg = courseDetail ? courseDetail.org : null;
const courseTitle = courseDetail ? courseDetail.name : courseId;
const courseAppsApiStatus = useSelector(getCourseAppsApiStatus);
const inProgress = useSelector(getLoadingStatus) === RequestStatus.IN_PROGRESS;
if (courseAppsApiStatus === RequestStatus.DENIED) {
return (
<PermissionDeniedAlert />
);
}
const AppHeader = () => (
<Header
courseNumber={courseNumber}
courseOrg={courseOrg}
courseTitle={courseTitle}
courseId={courseId}
/>
);
return (
<>
<Header
courseNumber={courseNumber}
courseOrg={courseOrg}
courseTitle={courseTitle}
courseId={courseId}
/>
{inProgress ? <Loading /> : AppHeader()}
{children}
<Footer />
{!inProgress && <Footer />}
</>
);
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { queryByTestId, render } from '@testing-library/react';
import { getConfig, initializeMockApp } from '@edx/frontend-platform';
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import initializeStore from './store';
import CourseAuthoringPage from './CourseAuthoringPage';
import PagesAndResources from './pages-and-resources/PagesAndResources';
import executeThunk from './utils';
import { fetchCourseApps } from './pages-and-resources/data/thunks';
const courseId = 'course-v1:edX+TestX+Test_Course';
let axiosMock;
let store;
let container;
function renderComponent() {
const wrapper = render(
<AppProvider store={store}>
<IntlProvider locale="en">
<CourseAuthoringPage courseId={courseId}>
<PagesAndResources courseId={courseId} />
</CourseAuthoringPage>
</IntlProvider>
</AppProvider>
,
);
container = wrapper.container;
}
const mockStore = async () => {
const apiBaseUrl = getConfig().STUDIO_BASE_URL;
const courseAppsApiUrl = `${apiBaseUrl}/api/course_apps/v1/apps`;
axiosMock.onGet(`${courseAppsApiUrl}/${courseId}`).reply(403, {
response: { status: 403 },
});
await executeThunk(fetchCourseApps(courseId), store.dispatch);
};
describe('DiscussionsSettings', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
});
test('renders permission error in case of 403', async () => {
await mockStore();
renderComponent();
expect(queryByTestId(container, 'permissionDeniedAlert')).toBeInTheDocument();
});
});

View File

@@ -14,7 +14,9 @@ import ResourceList from './resources/ResourcesList';
import { fetchCourseApps } from './data/thunks';
import { useModels } from '../generic/model-store';
import { getLoadingStatus } from './data/selectors';
import PagesAndResourcesProvider from './PagesAndResourcesProvider';
import { RequestStatus } from '../data/constants';
function PagesAndResources({ courseId, intl }) {
const { path, url } = useRouteMatch();
@@ -25,9 +27,12 @@ function PagesAndResources({ courseId, intl }) {
}, [courseId]);
const courseAppIds = useSelector(state => state.pagesAndResources.courseAppIds);
const loadingStatus = useSelector(getLoadingStatus);
// Each page here is driven by a course app
const pages = useModels('courseApps', courseAppIds);
if (loadingStatus === RequestStatus.IN_PROGRESS) {
return <></>;
}
return (
<PagesAndResourcesProvider courseId={courseId}>
<main className="container container-mw-md">

View File

@@ -2,3 +2,4 @@
export const getLoadingStatus = (status) => status.pagesAndResources.loadingStatus;
export const getSavingStatus = (status) => status.pagesAndResources.savingStatus;
export const getCourseAppsApiStatus = (status) => status.pagesAndResources.courseAppsApiStatus;

View File

@@ -8,6 +8,7 @@ const slice = createSlice({
courseAppIds: [],
loadingStatus: RequestStatus.IN_PROGRESS,
savingStatus: RequestStatus.SUCCESSFUL,
courseAppsApiStatus: {},
},
reducers: {
fetchCourseAppsSuccess: (state, { payload }) => {
@@ -19,6 +20,9 @@ const slice = createSlice({
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
},
updateCourseAppsApiStatus: (state, { payload }) => {
state.courseAppsApiStatus = payload.status;
},
},
});
@@ -26,6 +30,7 @@ export const {
fetchCourseAppsSuccess,
updateLoadingStatus,
updateSavingStatus,
updateCourseAppsApiStatus,
} = slice.actions;
export const {

View File

@@ -8,6 +8,7 @@ import {
fetchCourseAppsSuccess,
updateLoadingStatus,
updateSavingStatus,
updateCourseAppsApiStatus,
} from './slice';
/* eslint-disable import/prefer-default-export */
@@ -24,9 +25,10 @@ export function fetchCourseApps(courseId) {
}));
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.SUCCESSFUL }));
} catch (error) {
// TODO: We need generic error handling in the app for when a request just fails... in other
// parts of the app (proctored exam settings) we show a nice message and ask the user to
// reload/try again later.
if (error.response && error.response.status === 403) {
dispatch(updateCourseAppsApiStatus({ status: RequestStatus.DENIED }));
}
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED }));
}
};