diff --git a/src/containers/ResponseDisplay/PromptDisplay.jsx b/src/containers/ResponseDisplay/PromptDisplay.jsx
new file mode 100644
index 000000000..e8475c501
--- /dev/null
+++ b/src/containers/ResponseDisplay/PromptDisplay.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Collapsible, Card } from '@openedx/paragon';
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import PropTypes from 'prop-types';
+import messages from './messages';
+
+const PromptDisplay = ({
+ prompt, className, styling, headerTitle,
+}) => {
+ const intl = useIntl();
+ const msg = intl.formatMessage(messages.promptCollapsibleHeader);
+ return (
+
+ {msg} : msg}
+ >
+ { prompt }
+
+
+ );
+};
+
+PromptDisplay.propTypes = {
+ prompt: PropTypes.string.isRequired,
+ className: PropTypes.string.isRequired,
+ styling: PropTypes.string.isRequired,
+ headerTitle: PropTypes.bool.isRequired,
+};
+
+const SinglePromptDisplay = ({ prompt }) => (
+
+);
+
+SinglePromptDisplay.propTypes = {
+ prompt: PropTypes.string.isRequired,
+};
+
+const MultiplePromptDisplay = ({ prompt }) => (
+ <>
+
+
+ >
+);
+
+MultiplePromptDisplay.propTypes = {
+ prompt: PropTypes.string.isRequired,
+};
+
+export { SinglePromptDisplay, MultiplePromptDisplay };
diff --git a/src/containers/ResponseDisplay/ResponseDisplay.scss b/src/containers/ResponseDisplay/ResponseDisplay.scss
index 5d4e0fe53..16a8e04e9 100644
--- a/src/containers/ResponseDisplay/ResponseDisplay.scss
+++ b/src/containers/ResponseDisplay/ResponseDisplay.scss
@@ -4,6 +4,14 @@
overflow-y: hidden;
height: fit-content;
+ .prompt-display-single {
+ padding: var(--pgn-spacing-spacer-3) 0;
+ }
+
+ .prompt-display-multiple > .collapsible-basic .collapsible-trigger{
+ text-decoration: none!important;
+ }
+
.submission-files {
.submission-files-title {
padding: var(--pgn-spacing-spacer-3);
@@ -42,6 +50,10 @@
padding: var(--pgn-spacing-spacer-3) 0;
}
+ .response-display-card {
+ margin: var(--pgn-spacing-spacer-3) 0;
+ }
+
.response-display-text-content {
white-space: pre-line;
overflow: hidden;
diff --git a/src/containers/ResponseDisplay/index.jsx b/src/containers/ResponseDisplay/index.jsx
index 09cdac4bd..38ef3594a 100644
--- a/src/containers/ResponseDisplay/index.jsx
+++ b/src/containers/ResponseDisplay/index.jsx
@@ -14,7 +14,7 @@ import { fileUploadResponseOptions } from 'data/services/lms/constants';
import { getConfig } from '@edx/frontend-platform';
import SubmissionFiles from './SubmissionFiles';
import PreviewDisplay from './PreviewDisplay';
-
+import { SinglePromptDisplay, MultiplePromptDisplay } from './PromptDisplay';
import './ResponseDisplay.scss';
/**
@@ -26,13 +26,13 @@ export class ResponseDisplay extends React.Component {
this.purify = createDOMPurify(window);
}
+ get prompts() {
+ return this.props.prompts.map((item) => this.formattedHtml(item));
+ }
+
get textContents() {
const { text } = this.props.response;
-
- const formattedText = text
- .map((item) => item.replaceAll(/\.\.\/asset/g, `${getConfig().LMS_BASE_URL}/asset`))
- .map((item) => parse(this.purify.sanitize(item)));
-
+ const formattedText = text.map((item) => this.formattedHtml(item));
return formattedText;
}
@@ -46,15 +46,24 @@ export class ResponseDisplay extends React.Component {
);
}
+ formattedHtml(text) {
+ const cleanedText = text.replaceAll(/\.\.\/asset/g, `${getConfig().LMS_BASE_URL}/asset`);
+ return parse(this.purify.sanitize(cleanedText));
+ }
+
render() {
+ const { prompts } = this;
+ const multiPrompt = prompts.length > 1;
return (
+ {!multiPrompt &&
}
{this.allowFileUpload &&
}
{this.allowFileUpload &&
}
{
/* eslint-disable react/no-array-index-key */
this.textContents.map((textContent, index) => (
-
+
+ {multiPrompt && }
{textContent}
))
@@ -71,6 +80,7 @@ ResponseDisplay.defaultProps = {
},
fileUploadResponseConfig: fileUploadResponseOptions.none,
};
+
ResponseDisplay.propTypes = {
response: PropTypes.shape({
text: PropTypes.arrayOf(PropTypes.string),
@@ -83,11 +93,13 @@ ResponseDisplay.propTypes = {
fileUploadResponseConfig: PropTypes.oneOf(
Object.values(fileUploadResponseOptions),
),
+ prompts: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export const mapStateToProps = (state) => ({
response: selectors.grading.selected.response(state),
fileUploadResponseConfig: selectors.app.ora.fileUploadResponseConfig(state),
+ prompts: selectors.app.ora.prompts(state),
});
export const mapDispatchToProps = {};
diff --git a/src/containers/ResponseDisplay/index.test.jsx b/src/containers/ResponseDisplay/index.test.jsx
index 892540086..47fe2f184 100644
--- a/src/containers/ResponseDisplay/index.test.jsx
+++ b/src/containers/ResponseDisplay/index.test.jsx
@@ -13,11 +13,17 @@ jest.mock('data/redux', () => ({
app: {
ora: {
fileUploadResponseConfig: jest.fn((state) => state.fileUploadResponseConfig || 'optional'),
+ prompts: jest.fn((state) => state.prompts || ['prompt']),
},
},
},
}));
+jest.mock('./PromptDisplay', () => ({
+ SinglePromptDisplay: jest.fn(({ prompt }) => (Prompt: {prompt}
)),
+ MultiplePromptDisplay: jest.fn(({ prompt }) => (Prompt: {prompt}
)),
+}));
+
jest.mock('./SubmissionFiles', () => jest.fn(({ files }) => (
Files: {files.length}
)));
@@ -50,6 +56,7 @@ describe('ResponseDisplay', () => {
],
},
fileUploadResponseConfig: 'optional',
+ prompts: ['prompt one', 'prompt two'],
};
beforeAll(() => {
@@ -100,6 +107,18 @@ describe('ResponseDisplay', () => {
const textContents = container.querySelectorAll('.response-display-text-content');
expect(textContents).toHaveLength(0);
});
+
+ it('displays single prompt when only one prompt', () => {
+ render();
+ expect(screen.queryByTestId('prompt-single')).toBeInTheDocument();
+ expect(screen.queryByTestId('prompt-multiple')).not.toBeInTheDocument();
+ });
+
+ it('displays multiple prompts when there are multiple prompts', () => {
+ render();
+ expect(screen.queryByTestId('prompt-single')).not.toBeInTheDocument();
+ expect(screen.queryAllByTestId('prompt-multiple')).toHaveLength(2);
+ });
});
describe('mapStateToProps', () => {
@@ -109,6 +128,7 @@ describe('ResponseDisplay', () => {
files: ['file1', 'file2'],
},
fileUploadResponseConfig: 'required',
+ prompts: ['prompt'],
};
it('maps response from grading.selected.response selector', () => {
@@ -120,5 +140,10 @@ describe('ResponseDisplay', () => {
const mapped = mapStateToProps(testState);
expect(mapped.fileUploadResponseConfig).toEqual(selectors.app.ora.fileUploadResponseConfig(testState));
});
+
+ it('maps prompts from app.ora.prompts selector', () => {
+ const mapped = mapStateToProps(testState);
+ expect(mapped.prompts).toEqual(selectors.app.ora.prompts(testState));
+ });
});
});
diff --git a/src/containers/ResponseDisplay/messages.js b/src/containers/ResponseDisplay/messages.js
index e7387762d..de66c405b 100644
--- a/src/containers/ResponseDisplay/messages.js
+++ b/src/containers/ResponseDisplay/messages.js
@@ -46,6 +46,11 @@ const messages = defineMessages({
defaultMessage: 'Exceeded the allow download size',
description: 'Exceed the allow download size error message',
},
+ promptCollapsibleHeader: {
+ id: 'ora-grading.ResponseDisplay.Prompt.collapsibleHeader',
+ defaultMessage: 'Prompt',
+ description: 'Header for a collapsible that displays the assignment prompt',
+ },
});
export default messages;
diff --git a/src/data/redux/app/reducer.js b/src/data/redux/app/reducer.js
index f466e6071..aea71aa25 100644
--- a/src/data/redux/app/reducer.js
+++ b/src/data/redux/app/reducer.js
@@ -11,7 +11,7 @@ const initialState = {
isEnabled: false,
isGrading: false,
oraMetadata: {
- prompt: '',
+ prompts: [],
name: '',
type: '',
rubricConfig: null,
diff --git a/src/data/redux/app/reducer.test.js b/src/data/redux/app/reducer.test.js
index 4302fd2dd..c1384945d 100644
--- a/src/data/redux/app/reducer.test.js
+++ b/src/data/redux/app/reducer.test.js
@@ -17,7 +17,7 @@ describe('app reducer', () => {
});
test('populated, but empty ora metadata', () => {
const data = initialState.oraMetadata;
- expect(data.prompt).toEqual('');
+ expect(data.prompts).toEqual([]);
expect(data.name).toEqual('');
expect(data.type).toEqual('');
expect(data.rubricConfig).toEqual(null);
diff --git a/src/data/redux/app/selectors.js b/src/data/redux/app/selectors.js
index 815a73d61..4099f7e06 100644
--- a/src/data/redux/app/selectors.js
+++ b/src/data/redux/app/selectors.js
@@ -33,10 +33,10 @@ export const ora = {
*/
name: oraMetadataSelector(data => data.name),
/**
- * Returns the ORA Prompt
- * @return {string} - ORA prompt
+ * Returns the ORA Prompts
+ * @return {array[string]} - ORA prompt
*/
- prompt: oraMetadataSelector(data => data.prompt),
+ prompts: oraMetadataSelector(data => (data.prompts ? data.prompts.map((oraPrompt) => oraPrompt.description) : [])),
/**
* Returns the ORA type
* @return {string} - ORA type (team vs individual)
diff --git a/src/data/redux/app/selectors.test.js b/src/data/redux/app/selectors.test.js
index cb22fb3cb..36b7f16e6 100644
--- a/src/data/redux/app/selectors.test.js
+++ b/src/data/redux/app/selectors.test.js
@@ -18,7 +18,10 @@ const testState = {
},
oraMetadata: {
name: 'test-ora-name',
- prompt: 'test-ora-prompt',
+ prompts: [
+ { description: 'test-ora-prompt' },
+ { description: 'test-second-prompt' },
+ ],
type: 'test-ora-type',
fileUploadResponseConfig: 'file-upload-response-config',
rubricConfig: {
@@ -102,8 +105,8 @@ describe('app selectors unit tests', () => {
test('ora.name selector returns name from oraMetadata', () => {
testOraSelector(selectors.ora.name, oraMetadata.name);
});
- test('ora.prompt selector returns prompt from oraMetadata', () => {
- testOraSelector(selectors.ora.prompt, oraMetadata.prompt);
+ test('ora.prompts selector returns prompts from oraMetadata', () => {
+ testOraSelector(selectors.ora.prompts, ['test-ora-prompt', 'test-second-prompt']);
});
test('ora.type selector returns type from oraMetadata', () => {
testOraSelector(selectors.ora.type, oraMetadata.type);
diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js
index bc4c41ce3..c2ea60e55 100644
--- a/src/data/services/lms/api.js
+++ b/src/data/services/lms/api.js
@@ -16,7 +16,7 @@ import {
/**
* get('/api/initialize', { oraLocation })
* @return {
- * oraMetadata: { name, prompt, type ('individual' vs 'team'), rubricConfig, fileUploadResponseConfig },
+ * oraMetadata: { name, prompts, type ('individual' vs 'team'), rubricConfig, fileUploadResponseConfig },
* courseMetadata: { courseOrg, courseName, courseNumber, courseId },
* submissions: {
* [submissionUUID]: {