Add submissions page

This commit is contained in:
orosmatthew 2023-04-29 15:27:51 -04:00
parent 813e51a6e7
commit 1b412e8e93
9 changed files with 262 additions and 12 deletions

View File

@ -28,15 +28,16 @@ enum SubmissionState {
} }
model Submission { model Submission {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
state SubmissionState gradedAt DateTime?
actualOutput String state SubmissionState
message String? actualOutput String
team Team @relation(fields: [teamId], references: [id]) message String?
teamId Int team Team @relation(fields: [teamId], references: [id])
problem Problem @relation(fields: [problemId], references: [id]) teamId Int
problemId Int problem Problem @relation(fields: [problemId], references: [id])
problemId Int
} }
model Problem { model Problem {

View File

@ -2,6 +2,7 @@
<ul class="nav col-12 col-md-auto justify-content-center"> <ul class="nav col-12 col-md-auto justify-content-center">
<li><a href="/admin" class="nav-link px-2">Home</a></li> <li><a href="/admin" class="nav-link px-2">Home</a></li>
<li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li> <li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li>
<li><a href="/admin/submissions" class="nav-link px-2">Submissions</a></li>
<li><a href="/admin/problems" class="nav-link px-2">Problems</a></li> <li><a href="/admin/problems" class="nav-link px-2">Problems</a></li>
<li><a href="/admin/scoreboard" class="nav-link px-2">Scoreboard</a></li> <li><a href="/admin/scoreboard" class="nav-link px-2">Scoreboard</a></li>
<li><a href="/admin/teams" class="nav-link px-2">Teams</a></li> <li><a href="/admin/teams" class="nav-link px-2">Teams</a></li>

View File

@ -2,6 +2,7 @@ import type { Actions, PageServerLoad } from './$types';
import * as Diff from 'diff'; import * as Diff from 'diff';
import { error, redirect } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit';
import { db } from '$lib/server/prisma'; import { db } from '$lib/server/prisma';
import { SubmissionState } from '@prisma/client';
export const load = (async ({ params }) => { export const load = (async ({ params }) => {
const submissionId = parseInt(params.submissionId); const submissionId = parseInt(params.submissionId);
@ -33,11 +34,20 @@ export const actions = {
} }
const data = await request.formData(); const data = await request.formData();
const correct = data.get('correct'); const correct = data.get('correct');
const message = data.get('message');
if (!correct) { if (!correct) {
return { success: false }; return { success: false };
} }
const correctBool = correct.toString().toLowerCase() === 'true'; const correctBool = correct.toString().toLowerCase() === 'true';
await db.submission.delete({ where: { id: submissionId } }); const gradedTime = new Date();
await db.submission.update({
where: { id: submissionId },
data: {
state: correctBool ? SubmissionState.Correct : SubmissionState.Incorrect,
message: message ? message.toString() : '',
gradedAt: gradedTime
}
});
return { success: true }; return { success: true };
} }
} satisfies Actions; } satisfies Actions;

View File

@ -61,12 +61,12 @@
<div class="alert alert-success">Success!</div> <div class="alert alert-success">Success!</div>
{/if} {/if}
<a href="/admin/reviews" class="btn btn-outline-primary">Back</a> <a href="/admin/reviews" class="btn btn-outline-primary">All Reviews</a>
<div class="mt-3" id="diff" /> <div class="mt-3" id="diff" />
<form method="POST" action="?/submit" use:enhance> <form method="POST" action="?/submit" use:enhance>
<h5>Message</h5> <h5>Message</h5>
<textarea class="mb-3 form-control" /> <textarea name="message" class="mb-3 form-control" />
<div class="row justify-content-end"> <div class="row justify-content-end">
<div class="text-end"> <div class="text-end">

View File

@ -38,6 +38,15 @@ export const actions = {
return { success: false }; return { success: false };
} }
if (problem.realOutput === actual.toString()) { if (problem.realOutput === actual.toString()) {
await db.submission.create({
data: {
state: SubmissionState.Correct,
actualOutput: actual.toString(),
teamId: teamIdInt,
problemId: problemIdInt,
gradedAt: new Date()
}
});
return { success: true }; return { success: true };
} }
await db.submission.create({ await db.submission.create({

View File

@ -0,0 +1,25 @@
import { db } from '$lib/server/prisma';
import type { PageServerLoad } from './$types';
export const load = (async () => {
const submissions = await db.submission.findMany();
const problems = await db.problem.findMany();
const teams = await db.team.findMany();
return {
submissions: submissions.map((row) => {
return {
id: row.id,
createdAt: row.createdAt,
gradedAt: row.gradedAt,
message: row.message,
state: row.state,
problemName: problems.find((problem) => {
return problem.id == row.problemId;
})?.friendlyName,
teamName: teams.find((team) => {
return team.id == row.teamId;
})?.name
};
})
};
}) satisfies PageServerLoad;

View File

@ -0,0 +1,77 @@
<script lang="ts">
import { SubmissionState } from '@prisma/client';
import type { PageData } from './$types';
import { goto } from '$app/navigation';
export let data: PageData;
$: data.submissions.sort((a, b) => {
return b.createdAt.valueOf() - a.createdAt.valueOf();
});
</script>
<svelte:head>
<title>Submissions</title>
</svelte:head>
<h1 style="text-align:center" class="mb-4">Submissions</h1>
<p>Rows are color coded: Red - Incorrect, Green - Correct, Yellow - In Review</p>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Team</th>
<th>Problem</th>
<th>Submit Time</th>
<th>Graded Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{#each data.submissions as submission}
<tr
on:click={() => {
goto('/admin/submissions/' + submission.id.toString());
}}
class={(submission.state == SubmissionState.InReview
? 'table-warning'
: submission.state == SubmissionState.Correct
? 'table-success'
: submission.state == SubmissionState.Incorrect
? 'table-danger'
: '') + ' submission-row'}
>
<td>
{#if submission.teamName}
{submission.teamName}
{/if}
</td>
<td>
{#if submission.problemName}
{submission.problemName}
{/if}
</td>
<td
>{submission.createdAt.toLocaleDateString() +
' ' +
submission.createdAt.toLocaleTimeString()}</td
>
<td>
{#if submission.gradedAt}
{submission.gradedAt.toLocaleDateString() +
' ' +
submission.gradedAt.toLocaleTimeString()}
{/if}
</td>
<td>{submission.message ? submission.message : ''}</td>
</tr>
{/each}
</tbody>
</table>
<style>
.submission-row:hover {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,44 @@
import { error, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/prisma';
import Diff from 'diff';
import { SubmissionState } from '@prisma/client';
export const load = (async ({ params }) => {
const submissionId = parseInt(params.submissionId);
if (isNaN(submissionId)) {
throw error(400, 'Invalid submission');
}
const submission = await db.submission.findUnique({ where: { id: submissionId } });
if (!submission) {
throw redirect(302, '/admin/submissions');
}
const team = await db.team.findUnique({ where: { id: submission.teamId } });
if (!team) {
throw error(500, 'Invalid team');
}
const problem = await db.problem.findUnique({ where: { id: submission.problemId } });
if (!problem) {
throw error(500, 'Invalid problem');
}
let diff: string | null = null;
if (submission.state == SubmissionState.Incorrect) {
diff = Diff.createTwoFilesPatch(
'expected',
'actual',
problem.realOutput,
submission.actualOutput
);
}
return {
id: submission.id,
state: submission.state,
teamName: team.name,
problemName: problem.friendlyName,
submitTime: submission.createdAt,
gradedTime: submission.gradedAt,
message: submission.message,
diff: diff
};
}) satisfies PageServerLoad;

View File

@ -0,0 +1,83 @@
<script lang="ts">
import { SubmissionState } from '@prisma/client';
import type { PageData } from './$types';
import { onMount } from 'svelte';
import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-base';
import 'diff2html/bundles/css/diff2html.min.css';
export let data: PageData;
onMount(() => {
if (data.diff) {
const diff2htmlUi = new Diff2HtmlUI(document.getElementById('diff')!, data.diff, {
drawFileList: false,
matching: 'lines',
diffStyle: 'char',
outputFormat: 'side-by-side',
highlight: false,
fileContentToggle: false
});
diff2htmlUi.draw();
}
});
</script>
<svelte:head>
<title>Submission</title>
</svelte:head>
<h1 style="text-align:center" class="mb-4">Submission</h1>
<a href="/admin/submissions" class="mb-3 btn btn-outline-primary">All Submissions</a>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Team</th>
<th>Problem</th>
<th>Submit Time</th>
<th>Graded Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr
class={(data.state == SubmissionState.InReview
? 'table-warning'
: data.state == SubmissionState.Correct
? 'table-success'
: data.state == SubmissionState.Incorrect
? 'table-danger'
: '') + ' submission-row'}
>
<td>
{#if data.teamName}
{data.teamName}
{/if}
</td>
<td>
{#if data.problemName}
{data.problemName}
{/if}
</td>
<td>{data.submitTime.toLocaleDateString() + ' ' + data.submitTime.toLocaleTimeString()}</td>
<td>
{#if data.gradedTime}
{data.gradedTime.toLocaleDateString() + ' ' + data.gradedTime.toLocaleTimeString()}
{/if}
</td>
<td>{data.message ? data.message : ''}</td>
</tr>
</tbody>
</table>
{#if data.state == SubmissionState.InReview}
<div class="row">
<div class="text-center">
<a href={'/admin/diff/' + data.id} class="btn btn-warning">Review Submission</a>
</div>
</div>
{:else if data.state == SubmissionState.Incorrect}
<h2 style="text-align:center">Diff</h2>
<div id="diff" />
{/if}