Add submissions page
This commit is contained in:
parent
813e51a6e7
commit
1b412e8e93
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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">
|
||||||
|
@ -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({
|
||||||
|
25
web/src/routes/admin/submissions/+page.server.ts
Normal file
25
web/src/routes/admin/submissions/+page.server.ts
Normal 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;
|
77
web/src/routes/admin/submissions/+page.svelte
Normal file
77
web/src/routes/admin/submissions/+page.svelte
Normal 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>
|
@ -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;
|
83
web/src/routes/admin/submissions/[submissionId]/+page.svelte
Normal file
83
web/src/routes/admin/submissions/[submissionId]/+page.svelte
Normal 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}
|
Loading…
Reference in New Issue
Block a user