Skip to content

fix(studentView): Standardize grader's student view to match actual student experience #8000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,25 @@ if attempt.submitted? && !attempt.auto_grading
end
end

json.categoryGrades answer.selections.includes(:criterion).map do |selection|
criterion = selection.criterion

json.id selection.id
json.gradeId criterion&.id
json.categoryId selection.category_id
json.grade criterion ? criterion.grade : selection.grade
json.explanation criterion ? nil : selection.explanation
if can_grade || (@assessment.show_rubric_to_students? && @submission.published?)
json.categoryGrades answer.selections.includes(:criterion).map do |selection|
criterion = selection.criterion

json.id selection.id
json.gradeId criterion&.id
json.categoryId selection.category_id
json.grade criterion ? criterion.grade : selection.grade
json.explanation criterion ? nil : selection.explanation
end
end

posts = answer.submission.submission_questions.find_by(question_id: answer.question_id)&.discussion_topic&.posts
ai_generated_comment = posts&.select(&:is_ai_generated)&.last
if ai_generated_comment
json.aiGeneratedComment do
json.partial! ai_generated_comment
if can_grade
posts = answer.submission.submission_questions.find_by(question_id: answer.question_id)&.discussion_topic&.posts
ai_generated_comment = posts&.select(&:is_ai_generated)&.last
if ai_generated_comment
json.aiGeneratedComment do
json.partial! ai_generated_comment
end
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# frozen_string_literal: true
json.aiGradingEnabled question.ai_grading_enabled?
json.categories question.categories.each do |category|
json.id category.id
json.name category.name
json.maximumGrade category.criterions.map(&:grade).compact.max
json.isBonusCategory category.is_bonus_category
json.aiGradingEnabled question.ai_grading_enabled? if can_grade
if can_grade || (@assessment.show_rubric_to_students? && answer.submission.published?)
json.categories question.categories.each do |category|
json.id category.id
json.name category.name
json.maximumGrade category.criterions.map(&:grade).compact.max
json.isBonusCategory category.is_bonus_category

json.grades category.criterions.each do |criterion|
json.id criterion.id
json.grade criterion.grade
json.explanation format_ckeditor_rich_text(criterion.explanation)
json.grades category.criterions.each do |criterion|
json.id criterion.id
json.grade criterion.grade
json.explanation format_ckeditor_rich_text(criterion.explanation)
end
end
else
json.categories []
end
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ json.question do
json.description format_ckeditor_rich_text(question.description)
json.type question.question_type

json.partial! question, question: question.specific, can_grade: false, answer: @answer
json.partial! question, question: question.specific, can_grade: can_grade, answer: @answer
end
json.partial! specific_answer, answer: specific_answer, can_grade: false
json.partial! specific_answer, answer: specific_answer, can_grade: can_grade

if can_grade || @answer.submission.published?
json.grading do
json.grade @answer&.grade
end
end

# hide unpublished annotations in answer details
if @answer.actable_type == Course::Assessment::Answer::Programming.name
files = @answer.specific.files
json.partial! 'course/assessment/answer/programming/annotations', programming_files: files,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ const LastAttemptIndex: FC<Props> = (props) => {
</Accordion>
<AnswerDetails
answer={answer}
displaySettings={{
showPrivateTestCases: true,
showEvaluationTestCases: true,
showMcqMrqSolution: true,
showRubricBreakdown: true,
showPublicTestCasesOutput: true,
showPrivateTestCasesOutput: true,
showEvaluationTestCasesOutput: true,
showStdoutAndStderr: true,
}}
question={answer.question}
status={HistoryFetchStatus.COMPLETED}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';
import { formatLongDateTime } from 'lib/moment';

import { workflowStates } from '../../constants';
import { historyActions } from '../../reducers/history';
import { getAssessment } from '../../selectors/assessments';
import { getSubmissionQuestionHistory } from '../../selectors/history';
import { getSubmission } from '../../selectors/submissions';
import translations from '../../translations';
import AnswerDetails from '../AnswerDetails/AnswerDetails';
import TextResponseSolutions from '../TextResponseSolutions';
Expand All @@ -38,6 +41,10 @@ const AllAttemptsSequenceView: FC<Props> = (props) => {
const { answerDataById, allAnswers, selectedAnswerIds, question } =
useAppSelector(getSubmissionQuestionHistory(submissionId, questionId));

const assessment = useAppSelector(getAssessment);
const submission = useAppSelector(getSubmission);
const published = submission.workflowState === workflowStates.Published;

useEffect(() => {
const answerIdsToFetch =
selectedAnswerIds.filter(
Expand Down Expand Up @@ -94,6 +101,23 @@ const AllAttemptsSequenceView: FC<Props> = (props) => {
<AnswerDetails
key={answerId}
answer={answerDataById?.[answerId]?.details}
displaySettings={{
showEvaluationTestCases:
graderView || (published && assessment.showEvaluation),
showEvaluationTestCasesOutput: graderView,
showMcqMrqSolution:
graderView || (published && assessment.showMcqMrqSolution),
showPrivateTestCases:
graderView || (published && assessment.showPrivate),
showPrivateTestCasesOutput: graderView,
showPublicTestCasesOutput:
graderView || submission.showPublicTestCasesOutput,
showRubricBreakdown:
graderView ||
(published && assessment.showRubricToStudents),
showStdoutAndStderr:
graderView || submission.showStdoutAndStderr,
}}
question={question!}
status={answerDataById?.[answerId]?.status}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@ import CustomSlider from 'lib/components/extensions/CustomSlider';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import { formatLongDateTime } from 'lib/moment';

import { workflowStates } from '../../constants';
import { getAssessment } from '../../selectors/assessments';
import { getSubmissionQuestionHistory } from '../../selectors/history';
import { getSubmission } from '../../selectors/submissions';
import AnswerDetails from '../AnswerDetails/AnswerDetails';

interface Props {
questionId: number;
submissionId: number;
graderView: boolean;
}

const AllAttemptsTimelineView: FC<Props> = (props) => {
const { submissionId, questionId } = props;
const { submissionId, questionId, graderView } = props;

const dispatch = useAppDispatch();

const { answerDataById, allAnswers, question } = useAppSelector(
getSubmissionQuestionHistory(submissionId, questionId),
);

const assessment = useAppSelector(getAssessment);
const submission = useAppSelector(getSubmission);
const published = submission.workflowState === workflowStates.Published;

// sliderIndex is the uncommited index that is updated on drag
// displayedIndex is updated on drop or with any non-mouse (keyboard) events
// we distinguish these because we don't want to query each answer as user drags the slider
Expand Down Expand Up @@ -102,6 +110,21 @@ const AllAttemptsTimelineView: FC<Props> = (props) => {
<div className={sliderIndex === displayedIndex ? '' : 'opacity-60'}>
<AnswerDetails
answer={answerDetails}
displaySettings={{
showEvaluationTestCases:
graderView || (published && assessment.showEvaluation),
showEvaluationTestCasesOutput: graderView,
showMcqMrqSolution:
graderView || (published && assessment.showMcqMrqSolution),
showPrivateTestCases:
graderView || (published && assessment.showPrivate),
showPrivateTestCasesOutput: graderView,
showPublicTestCasesOutput:
graderView || submission.showPublicTestCasesOutput,
showRubricBreakdown:
graderView || (published && assessment.showRubricToStudents),
showStdoutAndStderr: graderView || submission.showStdoutAndStderr,
}}
question={question}
status={answerStatus}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const AllAttemptsContent: FC<ContentProps> = (props) => {
)}
{viewType === 'timeline' && (
<AllAttemptsTimelineView
graderView={graderView}
questionId={questionId}
submissionId={submissionId}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { formatLongDateTime } from 'lib/moment';
import messagesTranslations from 'lib/translations/messages';

import { HistoryFetchStatus } from '../../reducers/history';
import { AnswerDetailsProps } from '../../types';
import { AnswerDetailsProps, DisplaySettings } from '../../types';

import FileUploadDetails from './FileUploadDetails';
import ForumPostResponseDetails from './ForumPostResponseDetails';
Expand Down Expand Up @@ -100,19 +100,26 @@ const FetchedAnswerDetails = <T extends keyof typeof QuestionType>(

type AnswerDetailsComponentProps<T extends keyof typeof QuestionType> = {
status: HistoryFetchStatus;
displaySettings: DisplaySettings;
} & Partial<AnswerDetailsProps<T>>;

const AnswerDetails = <T extends keyof typeof QuestionType>(
props: AnswerDetailsComponentProps<T>,
): JSX.Element => {
const { answer, question, status } = props;
const { answer, question, status, displaySettings } = props;

const { t } = useTranslation();

const isAnswerRenderable =
answer && question && status === HistoryFetchStatus.COMPLETED;
if (isAnswerRenderable) {
return <FetchedAnswerDetails answer={answer!} question={question!} />;
return (
<FetchedAnswerDetails
answer={answer!}
displaySettings={displaySettings!}
question={question!}
/>
);
}
if (status === HistoryFetchStatus.ERRORED) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { AnswerDetailsProps } from '../../types';
const MultipleChoiceDetails = (
props: AnswerDetailsProps<QuestionType.MultipleChoice>,
): JSX.Element => {
const { question, answer } = props;
const { question, answer, displaySettings } = props;
const { showMcqMrqSolution } = displaySettings;
return (
<>
{question.options.map((option) => (
Expand All @@ -25,7 +26,7 @@ const MultipleChoiceDetails = (
<Typography
dangerouslySetInnerHTML={{ __html: option.option.trim() }}
style={
option.correct
showMcqMrqSolution && option.correct
? {
backgroundColor: green[50],
verticalAlign: 'middle',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { AnswerDetailsProps } from '../../types';
const MultipleResponseDetails = (
props: AnswerDetailsProps<QuestionType.MultipleResponse>,
): JSX.Element => {
const { question, answer } = props;
const { question, answer, displaySettings } = props;
const { showMcqMrqSolution } = displaySettings;
return (
<>
{question.options.map((option) => {
Expand All @@ -26,7 +27,7 @@ const MultipleResponseDetails = (
<Typography
dangerouslySetInnerHTML={{ __html: option.option.trim() }}
style={
option.correct
showMcqMrqSolution && option.correct
? {
backgroundColor: green[50],
verticalAlign: 'middle',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@ import { useAppDispatch } from 'lib/hooks/store';

import { AnswerDetailsProps } from '../../types';

import CodaveriFeedbackStatus from './ProgrammingComponent/CodaveriFeedbackStatus';
import FileContent from './ProgrammingComponent/FileContent';
import TestCases from './ProgrammingComponent/TestCases';

const ProgrammingAnswerDetails = (
props: AnswerDetailsProps<QuestionType.Programming>,
): JSX.Element => {
const { answer } = props;
const { answer, displaySettings } = props;
const annotations = answer.annotations ?? [];

const {
showPrivateTestCases,
showEvaluationTestCases,
showPublicTestCasesOutput,
showPrivateTestCasesOutput,
showEvaluationTestCasesOutput,
showStdoutAndStderr,
} = displaySettings;

const dispatch = useAppDispatch();

useEffect(() => {
Expand All @@ -35,8 +43,18 @@ const ProgrammingAnswerDetails = (
file={file}
/>
))}
<TestCases testCase={answer.testCases} />
<CodaveriFeedbackStatus status={answer.codaveriFeedback} />
<TestCases
showEvaluationTestCases={showEvaluationTestCases}
showEvaluationTestCasesOutput={showEvaluationTestCasesOutput}
showPrivateTestCases={showPrivateTestCases}
showPrivateTestCasesOutput={showPrivateTestCasesOutput}
showPublicTestCasesOutput={showPublicTestCasesOutput}
showStdoutAndStderr={showStdoutAndStderr}
testCase={answer.testCases}
/>
{/* might not need this component because unpublished annotations (i.e Codaveri) are not shown in Answer Details */}
{/* students can see this status bar in past attempts view, which is not relevant to them */}
{/* <CodaveriFeedbackStatus status={answer.codaveriFeedback} /> */}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ExpandableCode from 'lib/components/core/ExpandableCode';

interface Props {
result: TestCaseResult;
showTestCaseOutput: boolean;
}

const TestCaseClassName = {
Expand All @@ -16,7 +17,7 @@ const TestCaseClassName = {
};

const TestCaseRow: FC<Props> = (props) => {
const { result } = props;
const { result, showTestCaseOutput } = props;

const nameRegex = /\/?(\w+)$/;
const idMatch = result.identifier?.match(nameRegex);
Expand Down Expand Up @@ -56,9 +57,11 @@ const TestCaseRow: FC<Props> = (props) => {
<ExpandableCode>{result.expected || ''}</ExpandableCode>
</TableCell>

<TableCell className="w-full pt-1">
<ExpandableCode>{result.output || ''}</ExpandableCode>
</TableCell>
{showTestCaseOutput && (
<TableCell className="w-full pt-1">
<ExpandableCode>{result.output || ''}</ExpandableCode>
</TableCell>
)}

<TableCell>{testCaseIcon}</TableCell>
</TableRow>
Expand Down
Loading