Skip to content

Commit

Permalink
Merge branch 'integrate-grading-service-3' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
vivienherq authored Apr 11, 2024
2 parents 0ce6178 + 66ac5c3 commit f9b4cac
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 300 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@nextui-org/react": "^2.2.9",
"@nextui-org/react": "^2.2.10",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.24.1",
"axios": "^1.6.7",
Expand Down
299 changes: 112 additions & 187 deletions frontend/src/app/assignments/[id]/submissions/[sid]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
"use client";

import { MutableRefObject, useRef } from "react";
import Editor from "@monaco-editor/react";
import { ChangeEvent, SetStateAction, useEffect, useState } from "react";
import {
Tabs,
Tab,
Card,
CardBody,
Spacer,
Divider,
Code,
ButtonGroup,
Button,
Select,
SelectItem,
} from "@nextui-org/react";
import AssignmentService from "@/helpers/assignment-service/api-wrapper";
import GradingService from "@/helpers/grading-service/api-wrapper";
import { useQuery } from "@tanstack/react-query";
import { notFound } from "next/navigation";
import DateUtils from "../../../../../utils/dateUtils";
import * as monaco from "monaco-editor";
import FeedbackCodeEditor from "@/components/submission/FeedbackCodeEditor";
import FeedbackTabs from "@/components/submission/FeedbackTabs";
import FeedbackQuestion from "@/components/submission/FeedbackQuestion";

interface Props {
params: {
Expand All @@ -24,46 +24,21 @@ interface Props {
};
}

interface Item {
id: string;
label: string;
content: string[];
}

export default function SubmissionPage({ params }: Props) {
const editorRef: MutableRefObject<monaco.editor.IStandaloneCodeEditor | null> =
useRef(null);
// const [value, setValue] = useState("");
// const [language, setLanguage] = useState("python");
const userId = 1;
const [currentQuestion, setCurrentQuestion] = useState<number>(0);
const [currentQuestionId, setCurrentQuestionId] = useState("");
const [selectedSubmissionId, setSelectedSubmissionId] = useState("");

const feedback = {
line: 2,
hints: ["Incorrect else block for if ( ((x % 2) == 1) )"],
const handleQuestionChange = (questionNumber: number, questionId: string) => {
setCurrentQuestion(questionNumber);
setCurrentQuestionId(questionId);
};

const newDecoration: monaco.editor.IModelDeltaDecoration[] = [
{
range: new monaco.Range(feedback.line, 1, feedback.line, 1), // Highlight row 2
options: {
isWholeLine: true,
className: "bg-yellow-200",
},
},
];

const onMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
editorRef.current = editor;
editor.createDecorationsCollection(newDecoration);
editor.focus();
const handleSubmissionSelect = (e: ChangeEvent<HTMLSelectElement>) => {
setSelectedSubmissionId(e.target.value);
};

const code = `def is_odd(x):
if x % 2 == 1:
return False
else:
return True
`;

const {
data: assignment,
// isLoading,
Expand All @@ -72,65 +47,74 @@ export default function SubmissionPage({ params }: Props) {
queryKey: ["get-assignment", params.id],
queryFn: async () => {
const assignment = await AssignmentService.getAssignmentById(params.id);

return assignment;
},
});

if (isError) {
return notFound();
}
useEffect(() => {
if (assignment && assignment.questions) {
setCurrentQuestionId(assignment.questions[0]?.id ?? null);
}
}, [assignment]);

const tabs = [
{
id: "testcases",
label: "Test Cases",
content: [
"is_odd(1)",
"is_odd(2)",
"is_odd(3)",
"is_odd(0)",
"is_odd(-1)",
],
},
{
id: "feedback",
label: "Feedback",
content: [`Line ${feedback.line.toString()}: ${feedback.hints[0]}`],
const { data: submissions, refetch: refetchSubmissions } = useQuery({
queryKey: ["get-submissions", params.id, currentQuestionId],
queryFn: async () => {
const submissions =
await GradingService.getSubmissionByQuestionIdAndStudentId({
questionId: currentQuestionId,
studentId: userId,
});

const sortedSubmissions = submissions.sort(
(a, b) => a.createdOn - b.createdOn
);

return sortedSubmissions;
},
{
id: "grades",
label: "Grades",
content: [
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
],
});

const { data: testCases, refetch: refetchTestCases } = useQuery({
queryKey: ["get-testcases", params.id, currentQuestionId],
queryFn: async () => {
const testCases =
await AssignmentService.getQuestionTestCases(currentQuestionId);

return testCases;
},
];
});

const renderTabContent = (tabId: string, item: Item) => {
if (tabId === "testcases") {
return (
<div className="flex flex-col gap-4">
{item.content.map((testcase: string) => (
<Code color="default" key={testcase}>
{testcase}
</Code>
))}
</div>
);
} else if (tabId === "feedback") {
return item.content[0];
} else if (tabId === "grades") {
return item.content[0];
useEffect(() => {
refetchSubmissions();
refetchTestCases();
}, [currentQuestionId]);

useEffect(() => {
if (submissions && submissions.length > 0) {
setSelectedSubmissionId(submissions[0].id);
}
};
}, [submissions]);

if (isError) {
return notFound();
}

return (
<div>
{assignment && (
<div className="h-screen flex">
<div className="h-dvh flex p-4">
<div className="grid grid-cols-2 gap-4">
<div className="col-span-1 overflow-auto flex-1 max-w-1/2">
<div className="col-span-1 overflow-y-auto flex-1 max-w-1/2">
<div>
<ButtonGroup>
{assignment.questions?.map((question, index) => (
<Button
key={question.id}
onClick={() => handleQuestionChange(index, question.id)}
>{`${index + 1}`}</Button>
))}
</ButtonGroup>
</div>
<div className="flex gap-2">
<div>
<h1 className="text-3xl font-semibold ">
Expand All @@ -143,115 +127,56 @@ export default function SubmissionPage({ params }: Props) {
{DateUtils.parseTimestampToDate(assignment.deadline)}
</span>
</p>
{/* <p className="text-lg font-semibold">
Number of questions:{" "}
<span className="italic font-medium">
{assignment.numberOfQuestions}
</span>
</p> */}
</div>
</div>
</div>
<div className="flex px-0 py-4 mb-6">
<div className="w-full">
{/* Question title */}
<div className="flex space-x-4">
<div className="flex-1 mr-2 text-xl font-semibold">
Question 1: Two Sum
</div>
</div>
<Divider className="mt-4 mb-2" />
Given an array of integers nums and an integer target, return
indices of the two numbers such that they add up to target.
You may assume that each input would have exactly one
solution, and you may not use the same element twice. You can
return the answer in any order.
<div className="flex mt-3 w-full">
<div className="text-md text-justify">
<b>Example 1:</b>
<Card
shadow="none"
radius="none"
className="bg-gray-100"
fullWidth={true}
>
<CardBody>nums = [2,7,11,15], target = 9</CardBody>
</Card>
<Spacer y={3} />
<Card
shadow="none"
radius="none"
className="bg-gray-100"
fullWidth={true}
>
<CardBody>Output: [0,1]</CardBody>
</Card>
<Spacer y={3} />
<span>
<b>Explanation: </b>
<p>Because nums[0] + nums[1] == 9, we return [0, 1].</p>
</span>
<Spacer y={6} />
<b>Example 2:</b>
<Card
shadow="none"
radius="none"
className="bg-gray-100"
fullWidth={true}
>
<CardBody>Input: nums = [3,2,4], target = 6</CardBody>
</Card>
<Spacer y={3} />
<Card
shadow="none"
radius="none"
className="bg-gray-100"
fullWidth={true}
>
<CardBody>Output: [1,2]</CardBody>
</Card>
<Spacer y={6} />
<b>Constraints:</b>
<p>{"2 <= nums.length <= 104"}</p>
<p>{"-109 <= nums[i] <= 109"}</p>
<p>{"-109 <= target <= 109"}</p>
<b>Only one valid answer exists.</b>
</div>
</div>
</div>
</div>
{assignment.questions && (
<FeedbackQuestion
question={assignment.questions[currentQuestion]}
key={assignment.questions[currentQuestion].id}
/>
)}
</div>
<div className="col-span-1">
<div>
<Select
items={submissions}
label="Past Submissions"
placeholder="Select a submission"
className="max-w-xs"
onChange={handleSubmissionSelect}
>
{(submission) => (
<SelectItem key={submission.id} value={submission.id}>
{submission.createdOn}
</SelectItem>
)}
</Select>
</div>
<div className="row-span-1 border border-black">
<Editor
options={{
minimap: {
enabled: false,
},
readOnly: true,
}}
height="55vh"
// theme="vs-dark"
language="python"
// defaultValue={CODE_SNIPPETS[language]}
onMount={onMount}
value={code}
// onChange={(value) => setValue(value)}
/>
{submissions ? (
<FeedbackCodeEditor
submission={submissions.find(
(submission) => submission.id === selectedSubmissionId
)}
key={selectedSubmissionId}
/>
) : (
<FeedbackCodeEditor key={"0"} />
)}
</div>
<Spacer y={4} />
<div className="row-span-1">
<div className="flex w-full flex-col">
<Tabs color="primary" aria-label="Dynamic tabs" items={tabs}>
{(item) => (
<Tab key={item.id} title={item.label}>
<Card style={{ height: "30%" }}>
<CardBody>{renderTabContent(item.id, item)}</CardBody>
</Card>
</Tab>
{submissions ? (
<FeedbackTabs
submission={submissions.find(
(submission) => submission.id === selectedSubmissionId
)}
</Tabs>
</div>{" "}
testcases={testCases}
/>
) : (
<FeedbackTabs />
)}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function RootLayout({
<div>
<SideBar />
</div>
<div className="h-screen bg-white flex-1 p-4 text-black overflow-auto">
<div className="h-dvh bg-white flex-1 text-black overflow-auto">
{children}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function SideBar() {
const [isCollapsible, setIsCollapsible] = useState(false);
const [userInfo, setUserInfo] = useState<UserInfo>({} as UserInfo);
const wrapperClasses = classNames(
"h-screen px-4 pt-8 pb-4 bg-lightgrey text-black flex flex-col",
"h-dvh px-4 pt-8 pb-4 bg-lightgrey text-black flex flex-col",
{
["w-60"]: !isCollapsed,
["w-20"]: isCollapsed,
Expand Down
Loading

0 comments on commit f9b4cac

Please sign in to comment.