Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .husky/pre-push

This file was deleted.

6 changes: 6 additions & 0 deletions src/containers/ReviewModal/ReviewContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Col, Row } from '@openedx/paragon';
import { selectors } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';

import TurnitinDisplay from 'containers/TurnitinDisplay';
import ResponseDisplay from 'containers/ResponseDisplay';
import Rubric from 'containers/Rubric';
import ReviewErrors from './ReviewErrors';
Expand All @@ -17,6 +18,11 @@ import ReviewErrors from './ReviewErrors';
export const ReviewContent = ({ isFailed, isLoaded, showRubric }) => (isLoaded || isFailed) && (
<div className="content-block">
<div className="content-wrapper">
<Row className="flex-nowrap my-3">
<Col>
<TurnitinDisplay />
</Col>
</Row>
<ReviewErrors />
{isLoaded && (
<Row className="flex-nowrap m-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ exports[`ReviewContent component component render tests snapshot: failed, showRu
<div
className="content-wrapper"
>
<Row
className="flex-nowrap my-3"
>
<Col>
<injectIntl(ShimmedIntlComponent) />
</Col>
</Row>
<ReviewErrors />
</div>
</div>
Expand All @@ -19,6 +26,13 @@ exports[`ReviewContent component component render tests snapshot: hide rubric 1`
<div
className="content-wrapper"
>
<Row
className="flex-nowrap my-3"
>
<Col>
<injectIntl(ShimmedIntlComponent) />
</Col>
</Row>
<ReviewErrors />
<Row
className="flex-nowrap m-0"
Expand All @@ -41,6 +55,13 @@ exports[`ReviewContent component component render tests snapshot: show rubric 1`
<div
className="content-wrapper"
>
<Row
className="flex-nowrap my-3"
>
<Col>
<injectIntl(ShimmedIntlComponent) />
</Col>
</Row>
<ReviewErrors />
<Row
className="flex-nowrap m-0"
Expand Down
12 changes: 12 additions & 0 deletions src/containers/TurnitinDisplay/components/FileNameCell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';

export const FileNameCell = ({ value }) => (
<div className="text-truncate">{value.split('.')?.shift()}</div>
);

FileNameCell.propTypes = {
value: PropTypes.string.isRequired,
};

export default FileNameCell;
19 changes: 19 additions & 0 deletions src/containers/TurnitinDisplay/components/HyperlinkCell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Hyperlink } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';

export const HyperlinkCell = ({ value }) => {
const intl = useIntl();
return (
<Hyperlink destination={value} target="_blank">
{intl.formatMessage(messages.buttonViewerURLTitle)}
</Hyperlink>
);
};
HyperlinkCell.propTypes = {
value: PropTypes.string.isRequired,
};

export default HyperlinkCell;
41 changes: 41 additions & 0 deletions src/containers/TurnitinDisplay/components/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
fileNameTableHeader: {
id: 'ora-grading.TurnitinDisplay.FileNameCell.fileNameTitle',
defaultMessage: 'Responses',
description: 'Title for files attached to the submission',
},
URLTableHeader: {
id: 'ora-grading.TurnitinDisplay.FileNameCell.tableNameHeader',
defaultMessage: 'URL',
description: 'Title for the file name column in the table',
},
buttonViewerURLTitle: {
id: 'ora-grading.TurnitinDisplay.ButtonCell.URLButtonCellTitle',
defaultMessage: 'View in Turnitin',
description: 'Title for the button that opens the viewer in a new tab',
},
similarityReportsTitle: {
id: 'ora-grading.TurnitinDisplay.SimilarityReportsTitle',
defaultMessage: 'Turnitin Similarity Reports',
description: 'Title for the Turnitin Similarity Reports section',
},
noSimilarityReports: {
id: 'ora-grading.TurnitinDisplay.NoSimilarityReports',
defaultMessage: 'No Turnitin Similarity Reports to show',
description: 'Message to display when there are no Turnitin Similarity Reports to show',
},
viewerURLExpired: {
id: 'ora-grading.TurnitinDisplay.ViewerURLExpired',
defaultMessage: 'The Similarity Report URLs have a very short lifespan (less than 1 minute) after which it will no longer be valid. Once a user has been redirected to this URL, they will be given a session that will last for 1 hour. When expired, please refresh the page to get a new URL.',
description: 'Message to display when the viewer URL has expired',
},
viewerURLExpiredTitle: {
id: 'ora-grading.TurnitinDisplay.ViewerURLExpiredTitle',
defaultMessage: 'URLs expire quickly',
description: 'Title for the warning message when the viewer URL has expired',
},
});

export default messages;
114 changes: 114 additions & 0 deletions src/containers/TurnitinDisplay/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import {
Card,
Collapsible,
Icon,
DataTable,
Alert,
} from '@openedx/paragon';
import {
ArrowDropDown,
ArrowDropUp,
WarningFilled,
} from '@openedx/paragon/icons';
import { selectors } from 'data/redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './components/messages';

import FileNameCell from './components/FileNameCell';
import HyperlinkCell from './components/HyperlinkCell';

/**
* <TurnitinDisplay />
*/
export const TurnitinDisplay = ({ viewers, intl }) => {
const [isWarningOpen, setIsWarningOpen] = useState(true);
return (
<Card className="submission-files p-3">
{viewers.length ? (
<>
<Collapsible.Advanced defaultOpen>
<Collapsible.Trigger className="submission-files-title">
<h3>{intl.formatMessage(messages.similarityReportsTitle)}</h3>
<Collapsible.Visible whenClosed>
<Icon src={ArrowDropDown} />
</Collapsible.Visible>
<Collapsible.Visible whenOpen>
<Icon src={ArrowDropUp} />
</Collapsible.Visible>
</Collapsible.Trigger>
<Collapsible.Body className="submission-files-body">
<Alert
variant="warning"
dismissible
icon={WarningFilled}
show={isWarningOpen}
onClose={() => setIsWarningOpen(false)}
>
<Alert.Heading as="h4">
{intl.formatMessage(messages.viewerURLExpiredTitle)}
</Alert.Heading>
<p className="small mb-0">
{intl.formatMessage(messages.viewerURLExpired)}
</p>
</Alert>
<div className="submission-files-table">
<DataTable
columns={[
{
Header: intl.formatMessage(messages.fileNameTableHeader),
accessor: 'file_name',
Cell: FileNameCell,
},
{
Header: intl.formatMessage(messages.URLTableHeader),
accessor: 'url',
Cell: HyperlinkCell,
},
]}
data={viewers}
itemCount={viewers.length}
>
<DataTable.Table />
</DataTable>
</div>
</Collapsible.Body>
</Collapsible.Advanced>
<Card.Footer>{}</Card.Footer>
</>
) : (
<div className="submission-files-empty">
<WarningFilled />
<p>{intl.formatMessage(messages.noSimilarityReports)}</p>
</div>
)}
</Card>
);
};

TurnitinDisplay.defaultProps = {
viewers: [],
};

TurnitinDisplay.propTypes = {
viewers: PropTypes.arrayOf(
PropTypes.shape({
viewer_url: PropTypes.string.isRequired,
}),
),
// injected
intl: intlShape.isRequired,
};

export const mapStateToProps = (state) => ({
viewers: selectors.grading.selected.turnitinViewers(state),
});

export const mapDispatchToProps = {};

export default injectIntl(
connect(mapStateToProps, mapDispatchToProps)(TurnitinDisplay),
);
1 change: 1 addition & 0 deletions src/data/constants/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const RequestKeys = StrictDict({
prefetchPrev: 'prefetchPrev',
setLock: 'setLock',
submitGrade: 'submitGrade',
fetchTurnitinViewers: 'fetchTurnitinViewers',
});

export const ErrorCodes = StrictDict({
Expand Down
4 changes: 4 additions & 0 deletions src/data/redux/grading/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ const grading = createSlice({
},
};
},
loadTurnitinViewers: (state, { payload }) => ({
...state,
current: { ...state.current, turnitinViewers: payload },
}),
},
});

Expand Down
5 changes: 5 additions & 0 deletions src/data/redux/grading/selectors/selected.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ selected.submissionUUID = createSelector(
*/
selected.gradeStatus = createSelector([simpleSelectors.current], (current) => current.gradeStatus);

selected.turnitinViewers = createSelector(
[simpleSelectors.current],
(current) => current.turnitinViewers,
);

/**
* Returns the lock status for the selected submission
* @return {string} lock status
Expand Down
1 change: 1 addition & 0 deletions src/data/redux/requests/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const initialState = {
[RequestKeys.prefetchNext]: { status: RequestStates.inactive },
[RequestKeys.prefetchPrev]: { status: RequestStates.inactive },
[RequestKeys.submitGrade]: { status: RequestStates.inactive },
[RequestKeys.fetchTurnitinViewers]: { status: RequestStates.inactive },
};

// eslint-disable-next-line no-unused-vars
Expand Down
17 changes: 17 additions & 0 deletions src/data/redux/thunkActions/grading.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const loadSelectionForReview = (submissionUUIDs) => (dispatch) => {
dispatch(actions.grading.updateSelection(submissionUUIDs));
dispatch(actions.app.setShowReview(true));
dispatch(module.loadSubmission());
dispatch(module.loadTurnitinViewers());
}
};

Expand All @@ -60,6 +61,22 @@ export const loadSubmission = () => (dispatch, getState) => {
}));
};

export const loadTurnitinViewers = () => (dispatch, getState) => {
const submissionUUID = selectors.grading.selected.submissionUUID(getState());
dispatch(requests.fetchTurnitinViewers({
submissionUUID,
courseId: selectors.app.courseId(getState()),
onSuccess: (response) => {
dispatch(actions.grading.loadTurnitinViewers(response));
},
onFailure: (error) => {
if (error.response.status === ErrorStatuses.notFound) {
dispatch(actions.grading.loadTurnitinViewers([]));
}
},
}));
};

/**
* Start grading the current submission.
* Attempts to lock the submission, and on a success, sets the local grading state to
Expand Down
9 changes: 9 additions & 0 deletions src/data/redux/thunkActions/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ export const fetchSubmission = ({ submissionUUID, ...rest }) => (dispatch) => {
}));
};

export const fetchTurnitinViewers = ({ submissionUUID, courseId, ...rest }) => (dispatch) => {
dispatch(module.networkRequest({
requestKey: RequestKeys.fetchTurnitinViewers,
promise: api.fetchTurnitinViewers(submissionUUID, courseId),
...rest,
}));
};

/**
* Tracked setLock api method. tracked to the `setLock` request key.
* @param {string} submissionUUID - target submission id
Expand Down Expand Up @@ -125,4 +133,5 @@ export default StrictDict({
fetchSubmissionStatus,
setLock,
submitGrade,
fetchTurnitinViewers,
});
12 changes: 12 additions & 0 deletions src/data/services/lms/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ const fetchSubmission = (submissionUUID) => get(
}),
).then(response => response.data);

/**
* get(':courseId/api/v1/viewer-url/:submissionUUID')
* @return {
* url: <string>
* file_name: <string>
* }
*/
const fetchTurnitinViewers = (submissionUUID, courseId) => get(
stringifyUrl(`${urls.fetchTurnitinViewersUrl()}/${courseId}/api/v1/viewer-url/${submissionUUID}/`),
).then(response => response.data);

/**
* get('/api/submission/files', { oraLocation, submissionUUID })
* @return {
Expand Down Expand Up @@ -139,4 +150,5 @@ export default StrictDict({
updateGrade,
unlockSubmission,
batchUnlockSubmissions,
fetchTurnitinViewers,
});
2 changes: 2 additions & 0 deletions src/data/services/lms/urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const fetchSubmissionStatusUrl = () => `${baseEsgUrl()}submission/status`;
const fetchSubmissionLockUrl = () => `${baseEsgUrl()}submission/lock`;
const batchUnlockSubmissionsUrl = () => `${baseEsgUrl()}submission/batch/unlock`;
const updateSubmissionGradeUrl = () => `${baseEsgUrl()}submission/grade`;
const fetchTurnitinViewersUrl = () => `${baseUrl()}/platform-plugin-turnitin`;

const course = (courseId) => `${baseUrl()}/courses/${courseId}`;

Expand All @@ -30,6 +31,7 @@ export default StrictDict({
fetchSubmissionLockUrl,
batchUnlockSubmissionsUrl,
updateSubmissionGradeUrl,
fetchTurnitinViewersUrl,
baseUrl,
course,
openResponse,
Expand Down