@@ -72,7 +89,7 @@ const CourseImportPage = ({ intl, courseId }) => {
{intl.formatMessage(messages.description1)}
{intl.formatMessage(messages.description2)}
{intl.formatMessage(messages.description3)}
-
+
{importTriggered && }
diff --git a/src/import-page/CourseImportPage.test.jsx b/src/import-page/CourseImportPage.test.jsx
index 835931269..ccdf30db4 100644
--- a/src/import-page/CourseImportPage.test.jsx
+++ b/src/import-page/CourseImportPage.test.jsx
@@ -14,12 +14,17 @@ import CourseImportPage from './CourseImportPage';
import { getImportStatusApiUrl } from './data/api';
import { IMPORT_STAGES } from './data/constants';
import stepperMessages from './import-stepper/messages';
+import { getUserPermissionsUrl, getUserPermissionsEnabledFlagUrl } from '../generic/data/api';
+import { fetchUserPermissionsQuery, fetchUserPermissionsEnabledFlag } from '../generic/data/thunks';
+import { executeThunk } from '../utils';
let store;
let axiosMock;
let cookies;
const courseId = '123';
const courseName = 'About Node JS';
+const userId = 3;
+let userPermissionsData = { permissions: [] };
jest.mock('../generic/model-store', () => ({
useModel: jest.fn().mockReturnValue({
@@ -47,7 +52,7 @@ describe('', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
- userId: 3,
+ userId,
username: 'abc123',
administrator: true,
roles: [],
@@ -58,6 +63,12 @@ describe('', () => {
axiosMock
.onGet(getImportStatusApiUrl(courseId, 'testFileName.test'))
.reply(200, { importStatus: 1, message: '' });
+ axiosMock
+ .onGet(getUserPermissionsEnabledFlagUrl)
+ .reply(200, { enabled: false });
+ axiosMock
+ .onGet(getUserPermissionsUrl(courseId, userId))
+ .reply(200, userPermissionsData);
cookies = new Cookies();
cookies.get.mockReturnValue(null);
});
@@ -83,6 +94,30 @@ describe('', () => {
expect(getByText(messages.description3.defaultMessage)).toBeInTheDocument();
});
});
+ it('should render permissionDenied if incorrect permissions', async () => {
+ const { getByTestId } = render();
+ axiosMock.onGet(getUserPermissionsEnabledFlagUrl).reply(200, { enabled: true });
+ await executeThunk(fetchUserPermissionsEnabledFlag(), store.dispatch);
+ expect(getByTestId('permissionDeniedAlert')).toBeVisible();
+ });
+ it('should render without errors if correct permissions', async () => {
+ const { getByText } = render();
+ userPermissionsData = { permissions: ['manage_course_settings'] };
+ axiosMock.onGet(getUserPermissionsEnabledFlagUrl).reply(200, { enabled: true });
+ axiosMock.onGet(getUserPermissionsUrl(courseId, userId)).reply(200, userPermissionsData);
+ await executeThunk(fetchUserPermissionsQuery(courseId), store.dispatch);
+ await executeThunk(fetchUserPermissionsEnabledFlag(), store.dispatch);
+ await waitFor(() => {
+ expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
+ const importPageElement = getByText(messages.headingTitle.defaultMessage, {
+ selector: 'h2.sub-header-title',
+ });
+ expect(importPageElement).toBeInTheDocument();
+ expect(getByText(messages.description1.defaultMessage)).toBeInTheDocument();
+ expect(getByText(messages.description2.defaultMessage)).toBeInTheDocument();
+ expect(getByText(messages.description3.defaultMessage)).toBeInTheDocument();
+ });
+ });
it('should fetch status without clicking when cookies has', async () => {
cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.test' });
const { getByText } = render();
diff --git a/src/import-page/file-section/FileSection.jsx b/src/import-page/file-section/FileSection.jsx
index 012023f04..3a495f3cd 100644
--- a/src/import-page/file-section/FileSection.jsx
+++ b/src/import-page/file-section/FileSection.jsx
@@ -1,12 +1,12 @@
import React from 'react';
import {
+ FormattedMessage,
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
-import { Card, Dropzone } from '@edx/paragon';
-
+import { Alert, Card, Dropzone } from '@edx/paragon';
import { IMPORT_STAGES } from '../data/constants';
import {
getCurrentStage, getError, getFileName, getImportTriggered,
@@ -14,7 +14,7 @@ import {
import messages from './messages';
import { handleProcessUpload } from '../data/thunks';
-const FileSection = ({ intl, courseId }) => {
+const FileSection = ({ intl, courseId, viewOnly }) => {
const dispatch = useDispatch();
const importTriggered = useSelector(getImportTriggered);
const currentStage = useSelector(getCurrentStage);
@@ -30,21 +30,25 @@ const FileSection = ({ intl, courseId }) => {
subtitle={fileName && intl.formatMessage(messages.fileChosen, { fileName })}
/>
- {isShowedDropzone
- && (
- dispatch(handleProcessUpload(
- courseId,
- fileData,
- requestConfig,
- handleError,
- ))
- }
- accept={{ 'application/gzip': ['.tar.gz'] }}
- data-testid="dropzone"
- />
- )}
+ {!viewOnly && isShowedDropzone && (
+ dispatch(handleProcessUpload(
+ courseId,
+ fileData,
+ requestConfig,
+ handleError,
+ ))
+ }
+ accept={{ 'application/gzip': ['.tar.gz'] }}
+ data-testid="dropzone"
+ />
+ )}
+ {viewOnly && (
+
+
+
+ )}
);
@@ -53,6 +57,7 @@ const FileSection = ({ intl, courseId }) => {
FileSection.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
+ viewOnly: PropTypes.bool.isRequired,
};
export default injectIntl(FileSection);
diff --git a/src/import-page/file-section/FileSection.test.jsx b/src/import-page/file-section/FileSection.test.jsx
index 6d6bfb2d2..f6c1b1e15 100644
--- a/src/import-page/file-section/FileSection.test.jsx
+++ b/src/import-page/file-section/FileSection.test.jsx
@@ -14,7 +14,15 @@ const courseId = '123';
const RootWrapper = () => (
-
+
+
+
+);
+
+const RootWrapperViewOnly = () => (
+
+
+
);
@@ -27,6 +35,7 @@ describe('', () => {
username: 'abc123',
administrator: true,
roles: [],
+ permisions: [],
},
});
store = initializeStore();
@@ -43,6 +52,13 @@ describe('', () => {
expect(getByTestId('dropzone')).toBeInTheDocument();
});
});
+ it('should not render Dropzone when view is viewOnly', async () => {
+ const { getByText, queryByTestId, container } = render();
+ await waitFor(() => {
+ expect(queryByTestId(container, 'dropzone')).not.toBeInTheDocument();
+ expect(getByText(messages.viewOnlyAlert.defaultMessage)).toBeInTheDocument();
+ });
+ });
it('should work Dropzone', async () => {
const {
getByText, getByTestId, queryByTestId, container,
diff --git a/src/import-page/file-section/messages.js b/src/import-page/file-section/messages.js
index 6ad57b9a5..ecfb6939e 100644
--- a/src/import-page/file-section/messages.js
+++ b/src/import-page/file-section/messages.js
@@ -9,6 +9,10 @@ const messages = defineMessages({
id: 'course-authoring.import.file-section.chosen-file',
defaultMessage: 'File chosen: {fileName}',
},
+ viewOnlyAlert: {
+ id: 'course-authoring.import.file-section.view-only-alert',
+ defaultMessage: 'You have view only access to this page. If you feel you should have full access, please reach out to your course team admin to be given access.',
+ },
});
export default messages;
diff --git a/src/pages-and-resources/PagesAndResources.test.jsx b/src/pages-and-resources/PagesAndResources.test.jsx
index b30aedaaf..f97e60a61 100644
--- a/src/pages-and-resources/PagesAndResources.test.jsx
+++ b/src/pages-and-resources/PagesAndResources.test.jsx
@@ -8,6 +8,12 @@ import * as xpertUnitSummaryApi from './xpert-unit-summary/data/api';
const courseId = 'course-v1:edX+TestX+Test_Course';
+jest.mock('../generic/hooks', () => ({
+ useUserPermissions: jest.fn(() => ({
+ checkPermission: jest.fn(() => true),
+ })),
+}));
+
describe('PagesAndResources', () => {
beforeEach(() => {
jest.clearAllMocks();