diff --git a/client/app/bundles/course/assessment/submission/actions/answers/index.js b/client/app/bundles/course/assessment/submission/actions/answers/index.js
index 44331bb351f..630a413bb1d 100644
--- a/client/app/bundles/course/assessment/submission/actions/answers/index.js
+++ b/client/app/bundles/course/assessment/submission/actions/answers/index.js
@@ -33,28 +33,27 @@ export const updateClientVersion = (answerId, clientVersion) => (dispatch) =>
payload: { answer: { id: answerId, clientVersion } },
});
-const pollAutogradingJob =
- (jobUrl, submissionId, questionId, answerId) => (dispatch) => {
- pollJob(
- jobUrl,
- () => dispatch(getEvaluationResult(submissionId, answerId, questionId)),
- (errorData) => {
- dispatch({
- type: actionTypes.AUTOGRADE_FAILURE,
- questionId,
- payload: errorData,
- });
- dispatch(setNotification(translations.requestFailure));
- },
- JOB_POLL_DELAY_MS,
- );
- };
-
-export function submitAnswer(submissionId, answerId, rawAnswer, resetField) {
+// const pollAutogradingJob =
+// (jobUrl, submissionId, questionId, answerId) => (dispatch) => {
+// pollJob(
+// jobUrl,
+// () => dispatch(getEvaluationResult(submissionId, answerId, questionId)),
+// (errorData) => {
+// dispatch({
+// type: actionTypes.AUTOGRADE_FAILURE,
+// questionId,
+// payload: errorData,
+// });
+// dispatch(setNotification(translations.requestFailure));
+// },
+// JOB_POLL_DELAY_MS,
+// );
+// };
+
+export function submitAnswer(questionId, answerId, rawAnswer, resetField) {
const currentTime = Date.now();
const answer = formatAnswer(rawAnswer, currentTime);
const payload = { answer };
- const questionId = answer.questionId;
return (dispatch) => {
dispatch(updateClientVersion(answerId, currentTime));
@@ -73,16 +72,14 @@ export function submitAnswer(submissionId, answerId, rawAnswer, resetField) {
if (data.newSessionUrl) {
window.location = data.newSessionUrl;
} else if (data.jobUrl) {
- pollAutogradingJob(
- data.jobUrl,
- submissionId,
- questionId,
- answerId,
- )(dispatch);
+ dispatch({
+ type: actionTypes.AUTOGRADE_SUBMITTED,
+ payload: { questionId, jobUrl: data.jobUrl },
+ });
} else {
dispatch({
type: actionTypes.AUTOGRADE_SUCCESS,
- payload: data,
+ payload: { ...data, answerId },
});
// When an answer is submitted, the value of that field needs to be updated.
resetField(`${answerId}`, {
@@ -267,7 +264,6 @@ export function generateLiveFeedback(submissionId, answerId, questionId) {
});
}
-// TODO should each answer/question store its own feedback array?
export function fetchLiveFeedback(
answerId,
questionId,
diff --git a/client/app/bundles/course/assessment/submission/actions/index.js b/client/app/bundles/course/assessment/submission/actions/index.js
index 24f5db20428..99bb076bf19 100644
--- a/client/app/bundles/course/assessment/submission/actions/index.js
+++ b/client/app/bundles/course/assessment/submission/actions/index.js
@@ -1,3 +1,4 @@
+import GlobalAPI from 'api';
import CourseAPI from 'api/course';
import { setNotification } from 'lib/actions';
import pollJob from 'lib/helpers/jobHelpers';
@@ -12,7 +13,6 @@ import translations from '../translations';
import { buildErrorMessage, formatAnswers } from './utils';
const JOB_POLL_DELAY_MS = 500;
-const JOB_STAGGER_DELAY_MS = 400;
export function getEvaluationResult(submissionId, answerId, questionId) {
return (dispatch) => {
@@ -22,16 +22,20 @@ export function getEvaluationResult(submissionId, answerId, questionId) {
.then((data) => {
dispatch({
type: actionTypes.AUTOGRADE_SUCCESS,
- payload: data,
+ payload: { ...data, answerId },
});
})
.catch(() => {
dispatch(setNotification(translations.requestFailure));
- dispatch({ type: actionTypes.AUTOGRADE_FAILURE, questionId });
+ dispatch({ type: actionTypes.AUTOGRADE_FAILURE, questionId, answerId });
});
};
}
+export function getJobStatus(jobUrl) {
+ return GlobalAPI.jobs.get(jobUrl);
+}
+
export function fetchSubmission(id, onGetMonitoringSessionId) {
return (dispatch) => {
dispatch({ type: actionTypes.FETCH_SUBMISSION_REQUEST });
@@ -48,29 +52,6 @@ export function fetchSubmission(id, onGetMonitoringSessionId) {
window.location = data.newSessionUrl;
return;
}
- data.answers
- .filter((a) => a.autograding && a.autograding.path)
- .forEach((answer, index) => {
- setTimeout(() => {
- pollJob(
- answer.autograding.path,
- () =>
- dispatch(
- getEvaluationResult(
- id,
- answer.fields.id,
- answer.questionId,
- ),
- ),
- () =>
- dispatch({
- type: actionTypes.AUTOGRADE_FAILURE,
- questionId: answer.questionId,
- }),
- JOB_POLL_DELAY_MS,
- );
- }, JOB_STAGGER_DELAY_MS * index);
- });
if (data.monitoringSessionId !== undefined)
onGetMonitoringSessionId?.(data.monitoringSessionId);
dispatch({
diff --git a/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx b/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx
index 2c3319c626c..b3508607794 100644
--- a/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx
+++ b/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx
@@ -1,5 +1,6 @@
import { useRef, useState } from 'react';
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
+import { defineMessages } from 'react-intl';
import { Close, ThumbDown, ThumbUp } from '@mui/icons-material';
import {
Box,
@@ -8,6 +9,7 @@ import {
CardContent,
Drawer,
IconButton,
+ Tooltip,
Typography,
} from '@mui/material';
import { green, grey, orange, red, yellow } from '@mui/material/colors';
@@ -32,19 +34,25 @@ const styles = {
card: {
marginBottom: 1,
borderStyle: 'solid',
- borderWidth: 0.2,
+ borderWidth: 1.0,
borderColor: grey[400],
borderRadius: 2,
+ boxShadow: 'none',
minWidth: '300px',
maxWidth: '300px',
},
- header: {
+ cardActions: {
+ px: 0,
+ paddingTop: 0.5,
+ paddingBottom: 0,
display: 'flex',
- backgroundColor: orange[100],
- borderRadius: 2,
- padding: 1,
- borderBottomLeftRadius: 0,
- borderBottomRightRadius: 0,
+ },
+ cardContent: {
+ px: 1,
+ paddingTop: 0,
+ '&:last-child': {
+ paddingBottom: 1,
+ },
},
cardSelected: {
backgroundColor: yellow[100],
@@ -53,15 +61,53 @@ const styles = {
backgroundColor: orange.A100,
},
cardResolved: {
- opacity: 0.6,
- backgroundColor: green['100'],
+ borderColor: '#cecece',
+ backgroundColor: green[100],
+ color: grey[600],
},
cardDismissed: {
- opacity: 0.6,
- backgroundColor: red['100'],
+ borderColor: '#cecece',
+ backgroundColor: red[100],
+ color: grey[600],
+ },
+ cardActionButton: {
+ opacity: 1.0,
+ marginX: -0.5,
+ padding: 0.4,
+ transform: 'scale(0.86)',
+ transformOrigin: 'right',
+ },
+ cardActionButtonHighlightOnResolve: {
+ '&:disabled': {
+ color: green.A700,
+ },
+ },
+ cardActionButtonHighlightOnDismiss: {
+ '&:disabled': {
+ color: red.A700,
+ },
},
};
+const translations = defineMessages({
+ lineHeader: {
+ id: 'course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLineHeading',
+ defaultMessage: 'Line {linenum}',
+ },
+ likeItem: {
+ id: 'course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLike',
+ defaultMessage: 'Like',
+ },
+ dislikeItem: {
+ id: 'course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDislike',
+ defaultMessage: 'Dislike',
+ },
+ deleteItem: {
+ id: 'course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDelete',
+ defaultMessage: 'Dismiss',
+ },
+});
+
const ProgrammingFiles = ({
readOnly,
questionId,
@@ -95,32 +141,6 @@ const ProgrammingFiles = ({
}
};
- // const editorKeyboardHandler = {
- // handleKeyboard: (data, hash, keyString) => {
- // const selectedRow = editorRef.current?.editor?.selection?.cursor?.row;
- // const lastRow =
- // (editorRef.current?.editor?.session?.getLength() ?? 1) - 1;
- // if (selectedRow || selectedRow === 0) {
- // if (keyString === 'up') {
- // setSelectedLine(Math.max(selectedRow - 1, 0) + 1);
- // } else if (keyString === 'down') {
- // setSelectedLine(Math.min(selectedRow + 1, lastRow) + 1);
- // }
- // }
- // },
- // };
-
- // useEffect(() => {
- // editorRef.current?.editor?.keyBinding?.addKeyboardHandler(
- // editorKeyboardHandler,
- // );
- // return () => {
- // editorRef.current?.editor?.keyBinding?.removeKeyboardHandler(
- // editorKeyboardHandler,
- // );
- // };
- // });
-
const renderFeedbackCard = (feedbackItem) => {
let cardStyle = styles.card;
if (feedbackItem.state === 'resolved') {
@@ -131,6 +151,22 @@ const ProgrammingFiles = ({
cardStyle = { ...styles.card, ...styles.cardSelected };
}
+ const feedbackTooltipProps = {
+ placement: 'top',
+ slotProps: {
+ popper: {
+ modifiers: [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, -12],
+ },
+ },
+ ],
+ },
+ },
+ };
+
const focusEditorOnFeedbackLine = () => {
editorRef.current?.editor?.gotoLine(feedbackItem.linenum, 0);
editorRef.current?.editor?.selection?.setAnchor(
@@ -144,85 +180,94 @@ const ProgrammingFiles = ({
editorRef.current?.editor?.focus();
};
+ const renderLikeButton = () => (
+
+ {
+ dispatch({
+ type: actionTypes.LIVE_FEEDBACK_ITEM_MARK_RESOLVED,
+ payload: {
+ questionId,
+ path: 'main.py',
+ lineId: feedbackItem.id,
+ },
+ });
+ }}
+ sx={{
+ ...styles.cardActionButton,
+ ...styles.cardActionButtonHighlightOnResolve,
+ }}
+ >
+
+
+
+ );
+
+ const renderDislikeButton = () => (
+
+ {
+ dispatch({
+ type: actionTypes.LIVE_FEEDBACK_ITEM_MARK_DISMISSED,
+ payload: {
+ questionId,
+ path: 'main.py',
+ lineId: feedbackItem.id,
+ },
+ });
+ }}
+ sx={{
+ ...styles.cardActionButton,
+ ...styles.cardActionButtonHighlightOnDismiss,
+ }}
+ >
+
+
+
+ );
+
+ const renderDeleteButton = () => (
+
+ {
+ dispatch({
+ type: actionTypes.LIVE_FEEDBACK_ITEM_DELETE,
+ payload: {
+ questionId,
+ path: 'main.py',
+ lineId: feedbackItem.id,
+ },
+ });
+ }}
+ sx={{ ...styles.cardActionButton, marginRight: 1 }}
+ >
+
+
+
+ );
+
return (
-
- {feedbackItem.feedback}
-
- L{feedbackItem.linenum}
+ {t(translations.lineHeader, { linenum: feedbackItem.linenum })}
- {feedbackItem.state === 'resolved' && (
-
- {t({
- id: 'course.assessment.submission.answers.Programming.liveFeedbackItemResolved',
- defaultMessage: 'Item resolved.',
- })}
-
- )}
- {feedbackItem.state === 'dismissed' && (
-
- {t({
- id: 'course.assessment.submission.answers.Programming.liveFeedbackItemDismissed',
- defaultMessage: 'Item dismissed.',
- })}
-
- )}
- {
- // TODO: expose BE route to Codaveri feedback rating endpoint and call here
- dispatch({
- type: actionTypes.LIVE_FEEDBACK_ITEM_MARK_RESOLVED,
- payload: {
- questionId,
- path: 'main.py',
- lineId: feedbackItem.id,
- },
- });
- }}
- size="small"
- >
-
-
- {
- dispatch({
- type: actionTypes.LIVE_FEEDBACK_ITEM_MARK_DISMISSED,
- payload: {
- questionId,
- path: 'main.py',
- lineId: feedbackItem.id,
- },
- });
- }}
- size="small"
- >
-
-
- {
- dispatch({
- type: actionTypes.LIVE_FEEDBACK_ITEM_DELETE,
- payload: {
- questionId,
- path: 'main.py',
- lineId: feedbackItem.id,
- },
- });
- }}
- size="small"
- >
-
-
+ {renderLikeButton()}
+ {renderDislikeButton()}
+ {renderDeleteButton()}
+
+ {feedbackItem.feedback}
+
);
};
@@ -249,9 +294,7 @@ const ProgrammingFiles = ({
annotations = feedbackFiles['main.py'] ?? [];
}
const keyString = `editor-container-${index}`;
- const shouldOpenDrawer = annotations?.some(
- (feedbackItem) => feedbackItem.state === 'pending',
- );
+ const shouldOpenDrawer = annotations.length > 0;
return (
@@ -275,7 +318,14 @@ const ProgrammingFiles = ({
style: { alignContent: 'start', position: 'absolute' },
}}
open={shouldOpenDrawer}
- PaperProps={{ style: { position: 'absolute' } }}
+ PaperProps={{
+ style: {
+ position: 'absolute',
+ width: '315px',
+ alignContent: 'start',
+ border: 0,
+ },
+ }}
variant="persistent"
>
{annotations.map(renderFeedbackCard)}
@@ -322,7 +372,6 @@ const Programming = (props) => {
saveAnswerAndUpdateClientVersion={saveAnswerAndUpdateClientVersion}
/>
)}
- {/*
*/}
);
diff --git a/client/app/bundles/course/assessment/submission/constants.ts b/client/app/bundles/course/assessment/submission/constants.ts
index 699e105b80d..739f017a76f 100644
--- a/client/app/bundles/course/assessment/submission/constants.ts
+++ b/client/app/bundles/course/assessment/submission/constants.ts
@@ -154,6 +154,7 @@ const actionTypes = mirrorCreator([
'UNSUBMIT_SUCCESS',
'UNSUBMIT_FAILURE',
'AUTOGRADE_REQUEST',
+ 'AUTOGRADE_SUBMITTED',
'AUTOGRADE_SUCCESS',
'AUTOGRADE_FAILURE',
'AUTOGRADE_SAVING_SUCCESS',
diff --git a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditForm.jsx b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditForm.jsx
index 58ae1dae2c9..050c6140895 100644
--- a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditForm.jsx
+++ b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditForm.jsx
@@ -1,4 +1,4 @@
-import { lazy, Suspense, useEffect, useRef, useState } from 'react';
+import { lazy, Suspense, useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
@@ -86,7 +86,6 @@ const SubmissionEditForm = (props) => {
onSaveDraft,
onSubmit,
onSubmitAnswer,
- onFetchLiveFeedback,
onGenerateLiveFeedback,
onGenerateFeedback,
onReevaluateAnswer,
@@ -161,32 +160,6 @@ const SubmissionEditForm = (props) => {
}
}, [submissionTimeLimitAt]);
- const POLL_INTERVAL_MILLISECONDS = 2000;
- const pollerRef = useRef(null);
- const pollAllFeedback = () => {
- questionIds.forEach((id) => {
- const question = questions[id];
- const feedbackRequestToken =
- liveFeedback?.feedbackByQuestion?.[question.id]?.pendingFeedbackToken;
- if (feedbackRequestToken) {
- onFetchLiveFeedback(question.answerId, question.id);
- }
- });
- };
-
- useEffect(() => {
- // check for feedback from Codaveri on page load for each question
- pollerRef.current = setInterval(
- pollAllFeedback,
- POLL_INTERVAL_MILLISECONDS,
- );
-
- // clean up poller on unmount
- return () => {
- clearInterval(pollerRef.current);
- };
- });
-
const renderAutogradeSubmissionButton = () => {
if (graderView && submitted) {
return (
@@ -858,7 +831,6 @@ SubmissionEditForm.propTypes = {
onSubmit: PropTypes.func,
onSubmitAnswer: PropTypes.func,
onReevaluateAnswer: PropTypes.func,
- onFetchLiveFeedback: PropTypes.func,
onGenerateLiveFeedback: PropTypes.func,
onGenerateFeedback: PropTypes.func,
handleUnsubmit: PropTypes.func,
diff --git a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditStepForm.jsx b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditStepForm.jsx
index a2a845caf73..ae418e9ce06 100644
--- a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditStepForm.jsx
+++ b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionEditStepForm.jsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import Hotkeys from 'react-hot-keys';
import { FormattedMessage, injectIntl } from 'react-intl';
@@ -98,7 +98,6 @@ const SubmissionEditStepForm = (props) => {
onSaveDraft,
onSubmit,
onSubmitAnswer,
- onFetchLiveFeedback,
onGenerateLiveFeedback,
onGenerateFeedback,
onReevaluateAnswer,
@@ -157,32 +156,6 @@ const SubmissionEditStepForm = (props) => {
}
}, [submissionTimeLimitAt]);
- const POLL_INTERVAL_MILLISECONDS = 2000;
- const pollerRef = useRef(null);
- const pollAllFeedback = () => {
- questionIds.forEach((id) => {
- const question = questions[id];
- const feedbackRequestToken =
- liveFeedback?.feedbackByQuestion?.[question.id]?.pendingFeedbackToken;
- if (feedbackRequestToken) {
- onFetchLiveFeedback(question.answerId, question.id);
- }
- });
- };
-
- useEffect(() => {
- // check for feedback from Codaveri on page load for each question
- pollerRef.current = setInterval(
- pollAllFeedback,
- POLL_INTERVAL_MILLISECONDS,
- );
-
- // clean up poller on unmount
- return () => {
- clearInterval(pollerRef.current);
- };
- });
-
const handleNext = () => {
setMaxStep(Math.max(maxStep, stepIndex + 1));
setStepIndex(stepIndex + 1);
@@ -785,7 +758,6 @@ SubmissionEditStepForm.propTypes = {
onSubmit: PropTypes.func,
onSubmitAnswer: PropTypes.func,
onReevaluateAnswer: PropTypes.func,
- onFetchLiveFeedback: PropTypes.func,
onGenerateLiveFeedback: PropTypes.func,
onGenerateFeedback: PropTypes.func,
handleUnsubmit: PropTypes.func,
diff --git a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/index.jsx b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/index.jsx
index a4376f89009..04062a814b7 100644
--- a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/index.jsx
+++ b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/index.jsx
@@ -1,4 +1,4 @@
-import { Component } from 'react';
+import { Component, createRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import InsertDriveFile from '@mui/icons-material/InsertDriveFile';
@@ -27,6 +27,8 @@ import {
exitStudentView,
fetchSubmission,
finalise,
+ getEvaluationResult,
+ getJobStatus,
mark,
publish,
purgeSubmissionStore,
@@ -46,7 +48,7 @@ import {
submitAnswer,
} from '../../actions/answers';
import ProgressPanel from '../../components/ProgressPanel';
-import { workflowStates } from '../../constants';
+import actionTypes, { workflowStates } from '../../constants';
import {
answerShape,
assessmentShape,
@@ -66,6 +68,9 @@ import SubmissionEditStepForm from './SubmissionEditStepForm';
import SubmissionEmptyForm from './SubmissionEmptyForm';
import TimeLimitBanner from './TimeLimitBanner';
+const EVALUATE_POLL_INTERVAL_MILLISECONDS = 500;
+const FEEDBACK_POLL_INTERVAL_MILLISECONDS = 2000;
+
class VisibleSubmissionEditIndex extends Component {
constructor(props) {
super(props);
@@ -77,15 +82,29 @@ class VisibleSubmissionEditIndex extends Component {
: parseInt(stepString, 10) - 1;
this.state = { step };
+ this.evaluatePollerTimer = createRef(null);
+ this.feedbackPollerTimer = createRef(null);
}
componentDidMount() {
const { dispatch, match, setSessionId } = this.props;
dispatch(fetchSubmission(match.params.submissionId, setSessionId));
+
+ this.feedbackPollerTimer.current = setInterval(
+ this.handleLiveFeedbackPolling,
+ FEEDBACK_POLL_INTERVAL_MILLISECONDS,
+ );
+ this.evaluatePollerTimer.current = setInterval(
+ this.handleEvaluationPolling,
+ EVALUATE_POLL_INTERVAL_MILLISECONDS,
+ );
}
componentWillUnmount() {
const { dispatch } = this.props;
+
+ clearInterval(this.feedbackPollerTimer.current);
+ clearInterval(this.evaluatePollerTimer.current);
dispatch(purgeSubmissionStore());
}
@@ -97,6 +116,55 @@ class VisibleSubmissionEditIndex extends Component {
dispatch(autogradeSubmission(params.submissionId));
};
+ handleEvaluationPolling = () => {
+ const { answers, questionsFlags, dispatch, submission } = this.props;
+ Object.values(answers.initial).forEach((answer) => {
+ if (
+ questionsFlags[answer.questionId]?.isAutograding &&
+ questionsFlags[answer.questionId]?.jobUrl
+ ) {
+ getJobStatus(questionsFlags[answer.questionId].jobUrl).then(
+ (response) => {
+ switch (response.data.status) {
+ case 'submitted':
+ break;
+ case 'completed':
+ dispatch(
+ getEvaluationResult(
+ submission.id,
+ answer.id,
+ answer.questionId,
+ ),
+ );
+ break;
+ case 'errored':
+ dispatch({
+ type: actionTypes.AUTOGRADE_FAILURE,
+ answerId: answer.id,
+ questionId: answer.questionId,
+ });
+ break;
+ default:
+ throw new Error('Unknown job status');
+ }
+ },
+ );
+ }
+ });
+ };
+
+ handleLiveFeedbackPolling = () => {
+ const { questions, liveFeedback } = this.props;
+
+ Object.values(questions).forEach((question) => {
+ const feedbackRequestToken =
+ liveFeedback?.feedbackByQuestion?.[question.id]?.pendingFeedbackToken;
+ if (feedbackRequestToken) {
+ this.onFetchLiveFeedback(question.answerId, question.id);
+ }
+ });
+ };
+
handleMark = () => {
const {
dispatch,
@@ -188,11 +256,8 @@ class VisibleSubmissionEditIndex extends Component {
};
onSubmitAnswer = (answerId, answer, resetField) => {
- const {
- dispatch,
- match: { params },
- } = this.props;
- dispatch(submitAnswer(params.submissionId, answerId, answer, resetField));
+ const { dispatch } = this.props;
+ dispatch(submitAnswer(answer.questionId, answerId, answer, resetField));
};
onReevaluateAnswer = (answerId, questionId) => {
@@ -356,7 +421,6 @@ class VisibleSubmissionEditIndex extends Component {
isCodaveriEnabled={isCodaveriEnabled}
isSaving={isSaving}
maxStep={maxStep === undefined ? questionIds.length - 1 : maxStep}
- onFetchLiveFeedback={this.onFetchLiveFeedback}
onGenerateFeedback={this.onGenerateFeedback}
onGenerateLiveFeedback={this.onGenerateLiveFeedback}
onReevaluateAnswer={this.onReevaluateAnswer}
@@ -401,7 +465,6 @@ class VisibleSubmissionEditIndex extends Component {
isCodaveriEnabled={isCodaveriEnabled}
isSaving={isSaving}
maxStep={maxStep === undefined ? questionIds.length - 1 : maxStep}
- onFetchLiveFeedback={this.onFetchLiveFeedback}
onGenerateFeedback={this.onGenerateFeedback}
onGenerateLiveFeedback={this.onGenerateLiveFeedback}
onReevaluateAnswer={this.onReevaluateAnswer}
diff --git a/client/app/bundles/course/assessment/submission/reducers/questionsFlags.js b/client/app/bundles/course/assessment/submission/reducers/questionsFlags.js
index 07afc4f3064..512e755902c 100644
--- a/client/app/bundles/course/assessment/submission/reducers/questionsFlags.js
+++ b/client/app/bundles/course/assessment/submission/reducers/questionsFlags.js
@@ -16,6 +16,7 @@ export default function (state = {}, action) {
isAutograding:
Boolean(answer?.autograding) &&
answer?.autograding?.status === 'submitted',
+ jobUrl: answer?.autograding?.jobUrl,
jobError:
Boolean(answer?.autograding) &&
answer?.autograding?.status === 'errored',
@@ -34,6 +35,17 @@ export default function (state = {}, action) {
},
};
}
+ case actions.AUTOGRADE_SUBMITTED: {
+ const { questionId, jobUrl } = action.payload;
+ return {
+ ...state,
+ [questionId]: {
+ ...state[questionId],
+ isAutograding: true,
+ jobUrl,
+ },
+ };
+ }
case actions.REEVALUATE_SUCCESS:
case actions.AUTOGRADE_SUCCESS: {
const { questionId } = action.payload;
@@ -43,6 +55,7 @@ export default function (state = {}, action) {
...state[questionId],
isAutograding: false,
jobError: false,
+ jobErrorMessage: null,
},
};
}
diff --git a/client/locales/en.json b/client/locales/en.json
index fc63af7882f..49823a54dc3 100644
--- a/client/locales/en.json
+++ b/client/locales/en.json
@@ -2795,12 +2795,6 @@
"course.assessment.submission.answers.ForumPostResponse.TopicCard.viewTopicInNewTab": {
"defaultMessage": "View Topic"
},
- "course.assessment.submission.answers.Programming.liveFeedbackItemDismissed": {
- "defaultMessage": "Item dismissed."
- },
- "course.assessment.submission.answers.Programming.liveFeedbackItemResolved": {
- "defaultMessage": "Item resolved."
- },
"course.assessment.submission.answers.Programming.ProgrammingFile.downloadFile": {
"defaultMessage": "Download File"
},
@@ -2873,6 +2867,18 @@
"course.assessment.submission.comments": {
"defaultMessage": "Comments"
},
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDelete": {
+ "defaultMessage": "Dismiss"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDislike": {
+ "defaultMessage": "Dislike"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLike": {
+ "defaultMessage": "Like"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLineHeading": {
+ "defaultMessage": "Line {linenum}"
+ },
"course.assessment.submission.continue": {
"defaultMessage": "Continue"
},
diff --git a/client/locales/zh.json b/client/locales/zh.json
index 444517bf3fa..ab49a983654 100644
--- a/client/locales/zh.json
+++ b/client/locales/zh.json
@@ -2768,12 +2768,6 @@
"course.assessment.submission.answers.ForumPostResponse.TopicCard.viewTopicInNewTab": {
"defaultMessage": "查看主题"
},
- "course.assessment.submission.answers.Programming.liveFeedbackItemDismissed": {
- "defaultMessage": "项目已忽略。"
- },
- "course.assessment.submission.answers.Programming.liveFeedbackItemResolved": {
- "defaultMessage": "项目已解决。"
- },
"course.assessment.submission.answers.Programming.ProgrammingFile.downloadFile": {
"defaultMessage": "下载文件"
},
@@ -2846,6 +2840,18 @@
"course.assessment.submission.comments": {
"defaultMessage": "注释"
},
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDelete": {
+ "defaultMessage": "忽略"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDislike": {
+ "defaultMessage": "不喜欢"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLike": {
+ "defaultMessage": "喜欢"
+ },
+ "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLineHeading": {
+ "defaultMessage": "线 {linenum}"
+ },
"course.assessment.submission.continue": {
"defaultMessage": "继续"
},