Skip to content

Commit

Permalink
Add grading tasks admin page & sync repo for assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
ledoyen committed May 14, 2024
1 parent 4b88239 commit 27e929f
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"prettier": "prettier --check .",
"eslint": "eslint .",
"format": "prettier --write .",
"fmt": "prettier --write .",
"test:integration": "playwright test --trace on",
"test:unit": "vitest run"
},
Expand Down
11 changes: 10 additions & 1 deletion src/lib/api-backend/admin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { axiosAPI } from './../axios';
import type { Page, Table, UnparseableWebhook, UserForAdmin } from './../types';
import type { GradingTask, Page, Table, UnparseableWebhook, UserForAdmin } from './../types';
import { loadUser } from './../stores';

export const getTables = async (): Promise<Table[]> => {
Expand Down Expand Up @@ -50,3 +50,12 @@ export const deleteUnparseableWebhooks = async () => {
url: '/fapi/admin/unparseable_webhooks'
});
};

export const getGradingTasks = async (
page: number,
per_page: number
): Promise<Page<GradingTask>> => {
return await axiosAPI
.get<Page<GradingTask>>(`/fapi/admin/grading_tasks?page=${page}&per_page=${per_page}`)
.then((res) => res.data);
};
12 changes: 12 additions & 0 deletions src/lib/api-backend/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ export const getAssignment = async (
.get<Assignment>(`/fapi/module/${module_id}/assignment/${assignment_id}`)
.then((res) => res.data);
};

export const triggerGrading = async (module_id: string, assignment_id: string): Promise<void> => {
return await axiosAPI
.post(`/fapi/module/${module_id}/assignment/${assignment_id}/trigger-grading`)
.then((res) => res.data);
};

export const syncRepo = async (module_id: string, assignment_id: string): Promise<void> => {
return await axiosAPI
.post(`/fapi/module/${module_id}/assignment/${assignment_id}/sync-repo`)
.then((res) => res.data);
};
18 changes: 17 additions & 1 deletion src/lib/api-mock/admin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Page, Table, UnparseableWebhook, UserForAdmin } from './../types';
import type { GradingTask, Page, Table, UnparseableWebhook, UserForAdmin } from './../types';
import * as mock from './../mock';
import { loadUser } from './../stores';

Expand Down Expand Up @@ -47,3 +47,19 @@ export const getUnparseableWebhooks = async (
export const deleteUnparseableWebhooks = async () => {
mock.unparseable_webhooks.splice(0);
};

export const getGradingTasks = async (
page: number,
per_page: number
): Promise<Page<GradingTask>> => {
const start = page == 1 ? 0 : (page - 1) * per_page;
const end = start + per_page;
const items = mock.grading_tasks.slice(start, end);
return {
page: page,
per_page: per_page,
total_count: mock.grading_tasks.length,
total_page: Math.ceil(mock.grading_tasks.length / per_page),
data: items
};
};
24 changes: 24 additions & 0 deletions src/lib/api-mock/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,27 @@ export const getAssignment = async (
ongoing_run: a.ongoing_run
};
};

export const triggerGrading = async (module_id: string, assignment_id: string): Promise<void> => {
const assignment = mock.modules
.find((m) => m.id === module_id)
?.assignments.find((a) => a.id === assignment_id);
mock.grading_tasks.push({
assignment_id: assignment_id,
created_at: new Date(),
module_id: module_id,
provider_login: mock.users[0].provider_login,
repository_name: assignment?.repository_name ?? 'no matching assignment',
status: 'queued',
updated_at: new Date()
});
};

export const syncRepo = async (module_id: string, assignment_id: string): Promise<void> => {
const assignment = mock.modules
.find((m) => m.id === module_id)
?.assignments.find((a) => a.id === assignment_id);
if (assignment) {
assignment.linked = true;
}
};
5 changes: 4 additions & 1 deletion src/lib/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type {
UnparseableWebhook,
RunInfo,
Details,
CompleteRunInfo
CompleteRunInfo,
GradingTask
} from './types';

export const uuidv4 = () => {
Expand Down Expand Up @@ -176,6 +177,8 @@ for (let index = 0; index < unparseable_webhooks.length; index++) {
};
}

export const grading_tasks: GradingTask[] = [];

export const deleteUsers = (ids: string[]): void => {
users = users.filter((u) => !ids.includes(u.id));
};
Expand Down
10 changes: 10 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,13 @@ export type UnparseableWebhook = {
payload: string;
error: string;
};

export type GradingTask = {
module_id: string;
assignment_id: string;
provider_login: string;
status: string;
created_at: Date;
updated_at: Date;
repository_name: string;
};
7 changes: 7 additions & 0 deletions src/routes/admin/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
href="/admin/unparseable_webhooks">Unparseable Webhooks</a
>
</li>
<li class="nav-item">
<a
class="nav-link active"
aria-current={$page.url.pathname === '/admin/grading_tasks' ? 'page' : undefined}
href="/admin/grading_tasks">Grading Tasks</a
>
</li>
</nav>

<slot class="content" />
Expand Down
115 changes: 115 additions & 0 deletions src/routes/admin/grading_tasks/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import api from '$lib/api';
import '$css/table.css';
import '$css/grid.css';
let pageNumber = 1;
async function getItems() {
return await api.getGradingTasks(pageNumber, 15);
}
let itemPromise = getItems();
async function gotoPrevious() {
pageNumber -= 1;
itemPromise = getItems();
}
async function gotoNext() {
pageNumber += 1;
itemPromise = getItems();
}
</script>

<div class="text-column">
<h3>Users</h3>

{#await itemPromise}
<p class="p-white">...loading items</p>
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
{:then page}
<table class="table">
<thead>
<tr>
<th scope="col">Created at</th>
<th scope="col">Updated at</th>
<th scope="col">Module ID</th>
<th scope="col">Assignment ID</th>
<th scope="col">Provider login</th>
<th scope="col">Repository</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{#each page.data as item}
<tr>
<td>{item.created_at.toISOString()}</td>
<td>{item.updated_at.toISOString()}</td>
<td>{item.module_id}</td>
<td>{item.assignment_id}</td>
<td>{item.provider_login}</td>
<td>{item.repository_name}</td>
<td>{item.status}</td>
</tr>
{/each}
</tbody>
</table>
{#if page.total_page > 1}
<div class="row-centered">
<div class="col-auto">
{#if pageNumber > 1}
<button
type="button"
class="btn-smooth btn-pagination"
aria-label="Previous"
on:click={gotoPrevious}
>
<Icon icon="grommet-icons:caret-previous" inline />
</button>
{/if}
</div>
<div class="col-auto">
Page {pageNumber}
</div>
<div class="col-auto">
{#if pageNumber < page.total_page}
<button
type="button"
class="btn-smooth btn-pagination"
aria-label="Next"
on:click={gotoNext}
>
<Icon icon="grommet-icons:caret-next" inline />
</button>
{/if}
</div>
</div>
{/if}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
</div>

<style>
.btn-smooth {
background: none;
border: none;
}
.btn-smooth:hover {
background: lightgray;
border-radius: 6px;
}
.btn-pagination {
color: cornflowerblue;
font-size: 43px;
}
.col-auto {
display: flex;
justify-content: center;
}
</style>
5 changes: 5 additions & 0 deletions src/routes/admin/grading_tasks/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const csr = true;

// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in production
export const prerender = true;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
export let data;
const computeGrade = (a: Assignment): number => {
if (a.latest_run !== undefined) {
if (a.latest_run) {
let grade = 0;
let max_grade = 0;
for (const detail of a.latest_run.details) {
Expand Down Expand Up @@ -79,6 +79,19 @@
</div>
<div>Coefficient: {assignment.factor_percentage} %</div>
</div>
{#if assignment.repo_linked}
<div class="row disabled center-v">
<div class="mr-1 icon">
<Icon icon="emojione:rocket" inline style="font-size: 24px;" />
</div>
<button
type="button"
on:click={() => api.triggerGrading(data.moduleId, data.assignmentId)}
class="col-sm-1 btn btn-success"
>Trigger grading
</button>
</div>
{/if}
{#if assignment.locked}
<div class="row red center-v">
<div class="mr-1 icon">
Expand All @@ -105,14 +118,20 @@
>
{#if !assignment.repo_linked}
<div class="red bold">(missing)</div>
<button
type="button"
on:click={() => api.syncRepo(data.moduleId, data.assignmentId)}
class="col-sm-1 btn-link"
><Icon icon="tabler:refresh" inline style="font-size: 24px;" /> Sync
</button>
{/if}
</div>
</div>
<div class="row center-v content-line">
<Icon icon="bi:gear-wide-connected" inline style="font-size: 40px;" />
<div class="column ml-1">
<div>Grading state:</div>
{#if assignment.ongoing_run !== undefined}
{#if assignment.ongoing_run}
<a href={assignment.ongoing_run.grading_log_url}>
<div class="row green text-left">
<div class="bold">Ongoing</div>
Expand All @@ -122,7 +141,7 @@
</div>
</a>
{/if}
{#if assignment.latest_run !== undefined}
{#if assignment.latest_run}
<div>
Latest run: <a
class="link blue"
Expand All @@ -134,7 +153,7 @@
</div>
</div>
</div>
{#if assignment.latest_run !== undefined}
{#if assignment.latest_run}
<h3 class="black bold mb-0">Details of latest run:</h3>
<ul class="details">
{#each assignment.latest_run.details as detail}
Expand Down Expand Up @@ -282,4 +301,23 @@
transform: rotate(360deg);
}
}
.btn {
/*border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); */
border-radius: 0.375rem;
}
.btn-success {
background-color: #198754;
}
.btn-link {
background: none !important;
border: none;
padding: 0 !important;
font-family: arial, sans-serif;
color: #069;
text-decoration: underline;
cursor: pointer;
}
</style>

0 comments on commit 27e929f

Please sign in to comment.