Add submissions page
This commit is contained in:
parent
813e51a6e7
commit
1b412e8e93
@ -28,15 +28,16 @@ enum SubmissionState {
|
||||
}
|
||||
|
||||
model Submission {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
state SubmissionState
|
||||
actualOutput String
|
||||
message String?
|
||||
team Team @relation(fields: [teamId], references: [id])
|
||||
teamId Int
|
||||
problem Problem @relation(fields: [problemId], references: [id])
|
||||
problemId Int
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
gradedAt DateTime?
|
||||
state SubmissionState
|
||||
actualOutput String
|
||||
message String?
|
||||
team Team @relation(fields: [teamId], references: [id])
|
||||
teamId Int
|
||||
problem Problem @relation(fields: [problemId], references: [id])
|
||||
problemId Int
|
||||
}
|
||||
|
||||
model Problem {
|
||||
|
@ -2,6 +2,7 @@
|
||||
<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/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/scoreboard" class="nav-link px-2">Scoreboard</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 { error, redirect } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/prisma';
|
||||
import { SubmissionState } from '@prisma/client';
|
||||
|
||||
export const load = (async ({ params }) => {
|
||||
const submissionId = parseInt(params.submissionId);
|
||||
@ -33,11 +34,20 @@ export const actions = {
|
||||
}
|
||||
const data = await request.formData();
|
||||
const correct = data.get('correct');
|
||||
const message = data.get('message');
|
||||
if (!correct) {
|
||||
return { success: false };
|
||||
}
|
||||
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 };
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
@ -61,12 +61,12 @@
|
||||
<div class="alert alert-success">Success!</div>
|
||||
{/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" />
|
||||
|
||||
<form method="POST" action="?/submit" use:enhance>
|
||||
<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="text-end">
|
||||
|
@ -38,6 +38,15 @@ export const actions = {
|
||||
return { success: false };
|
||||
}
|
||||
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 };
|
||||
}
|
||||
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