Skip to content

Commit

Permalink
refactor!: allow lab rankings to be submitted at 0th round
Browse files Browse the repository at this point in the history
  • Loading branch information
BastiDood committed Aug 9, 2024
1 parent c0a38dc commit ac3ad9c
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 85 deletions.
42 changes: 24 additions & 18 deletions app/src/routes/dashboard/(admin)/drafts/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ export const actions = {
if (user === null) error(401);
if (!user.is_admin || user.user_id === null || user.lab_id !== null) error(403);

const isValid = await db.isValidTotalLabQuota();
if (!isValid) error(403);

const data = await request.formData();
const rounds = parseInt(validateString(data.get('rounds')), 10);
const initDraft = await db.initDraft(rounds);
Expand All @@ -54,29 +51,38 @@ export const actions = {
if (user === null) error(401);
if (!user.is_admin || user.user_id === null || user.lab_id !== null) error(403);

const isValid = await db.isValidTotalLabQuota();
if (!isValid) return fail(498);

const data = await request.formData();
const draft = BigInt(validateString(data.get('draft')));

const { labCount, studentCount } = await db.getLabCountAndStudentCount(draft);
assert(labCount > 0);
if (studentCount <= 0) error(403);
if (studentCount <= 0) return fail(497);

await db.begin(async db => {
const incrementDraftRound = await db.incrementDraftRound(draft);
db.logger.info({ incrementDraftRound });
assert(incrementDraftRound !== null);
assert(incrementDraftRound.curr_round !== null);

const postDraftRoundStartedNotification = await db.postDraftRoundStartedNotification(
draft,
incrementDraftRound.curr_round,
);
db.logger.info({ postDraftRoundStartedNotification });
await db.notifyDraftChannel();
while (true) {
const incrementDraftRound = await db.incrementDraftRound(draft);
assert(incrementDraftRound !== null, 'Cannot start a non-existent draft.');
db.logger.info({ incrementDraftRound });

const postDraftRoundStartedNotification = await db.postDraftRoundStartedNotification(
draft,
incrementDraftRound.curr_round,
);
db.logger.info({ postDraftRoundStartedNotification });
await db.notifyDraftChannel();

// Pause at the lottery rounds
if (incrementDraftRound.curr_round === null) break;

const autoAcknowledgeLabsWithoutPreferences = await db.autoAcknowledgeLabsWithoutPreferences(draft);
db.logger.info({ autoAcknowledgeLabsWithoutPreferences });

const ackCount = await db.autoAcknowledgeLabsWithoutPreferences(draft);
db.logger.info({ autoAcknowledgeLabsWithoutPreferences: ackCount });
assert(ackCount <= labCount);
const count = await db.getPendingLabCountInDraft(draft);
if (count > 0) break;
}
});
},
async intervene({ locals: { db }, cookies, request }) {
Expand Down
76 changes: 33 additions & 43 deletions app/src/routes/dashboard/(admin)/drafts/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,20 @@
</script>

{#if draft === null}
{#if labs.some(({ quota }) => quota > 0)}
<div class="grid grid-cols-1 gap-4 sm:grid-cols-[auto_1fr]">
<div class="prose dark:prose-invert">
<h2>Start a New Draft</h2>
<p>
Welcome to the <strong>Draft Ranking Automated Processor</strong>! There are currently no drafts
happening at the moment, but as an administrator, you have the authorization to start a new one.
</p>
<p>
To begin, simply provide the the maximum number of rounds for the upcoming draft. This has
historically been set to <strong>5</strong>.
</p>
</div>
<InitForm />
<div class="grid grid-cols-1 gap-4 sm:grid-cols-[auto_1fr]">
<div class="prose dark:prose-invert">
<h2>Start a New Draft</h2>
<p>
Welcome to the <strong>Draft Ranking Automated Processor</strong>! There are currently no drafts
happening at the moment, but as an administrator, you have the authorization to start a new one.
</p>
<p>
To begin, simply provide the the maximum number of rounds for the upcoming draft. This has historically
been set to <strong>5</strong>.
</p>
</div>
{:else}
<WarningAlert
>The total quota of all labs is currently zero. Please <a href="/dashboard/labs/" class="anchor">allocate</a
> at least one slot to a lab to proceed.</WarningAlert
>
{/if}
<InitForm />
</div>
{:else if draft.curr_round === null}
<div class="grid grid-cols-1 gap-4 md:grid-cols-[auto_1fr]">
<div class="prose dark:prose-invert">
Expand Down Expand Up @@ -90,31 +83,23 @@
</div>
{:else if draft.curr_round > 0}
<Dashboard {labs} {records} {available} {selected} round={draft.curr_round} />
{:else}
{:else if available.length > 0}
<div class="grid grid-cols-1 gap-4 sm:grid-cols-[auto_1fr]">
<div class="space-y-4">
{#if available.length > 0}
<section class="prose dark:prose-invert">
<h2>Registered Students</h2>
<p>
There are currently <strong>{available.length}</strong> students who have registered for this
draft. Press the <strong>"Start Draft"</strong> button to close registration and start the draft
automation.
</p>
<p>
Lab heads will be notified when the first round begins. The draft proceeds to the next round
when all lab heads have submitted their preferences. This process repeats until the configured
maximum number of rounds has elapsed, after which the draft pauses until an administrator
<em>manually</em> proceeds with the lottery stage.
</p>
</section>
<StartForm draft={draft.draft_id} />
{:else}
<WarningAlert
>No students have registered for this draft yet. The draft cannot proceed until at least one student
participates.</WarningAlert
>
{/if}
<section class="prose dark:prose-invert">
<h2>Registered Students</h2>
<p>
There are currently <strong>{available.length}</strong> students who have registered for this draft.
Press the <strong>"Start Draft"</strong> button to close registration and start the draft automation.
</p>
<p>
Lab heads will be notified when the first round begins. The draft proceeds to the next round when
all lab heads have submitted their preferences. This process repeats until the configured maximum
number of rounds has elapsed, after which the draft pauses until an administrator
<em>manually</em> proceeds with the lottery stage.
</p>
</section>
<StartForm draft={draft.draft_id} />
</div>
<nav class="list-nav w-full">
<ul class="list">
Expand All @@ -124,4 +109,9 @@
</ul>
</nav>
</div>
{:else}
<WarningAlert
>No students have registered for this draft yet. The draft cannot proceed until at least one student
participates.</WarningAlert
>
{/if}
15 changes: 6 additions & 9 deletions app/src/routes/dashboard/(admin)/drafts/InterveneForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@
return async ({ update, result }) => {
submitter.disabled = false;
await update();
switch (result.type) {
case 'success':
toast.trigger({
message: 'Successfully applied the interventions.',
background: 'variant-filled-success',
});
break;
default:
break;
if (result.type === 'success') {
toast.trigger({
message: 'Successfully applied the interventions.',
background: 'variant-filled-success',
});
return;
}
};
}}
Expand Down
26 changes: 25 additions & 1 deletion app/src/routes/dashboard/(admin)/drafts/StartForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
import type { Draft } from 'drap-model/draft';
import { assert } from '$lib/assert';
import { enhance } from '$app/forms';
import { getToastStore } from '@skeletonlabs/skeleton';
// eslint-disable-next-line init-declarations
export let draft: Draft['draft_id'];
const toast = getToastStore();
</script>

<form
Expand All @@ -18,9 +21,30 @@
assert(submitter !== null);
assert(submitter instanceof HTMLButtonElement);
submitter.disabled = true;
return async ({ update }) => {
return async ({ update, result }) => {
submitter.disabled = false;
await update();
if (result.type === 'failure') {
switch (result.status) {
case 497:
toast.trigger({
message: 'Cannot start the draft when there are not enough participants.',
background: 'variant-filled-error',
autohide: false,
});
break;
case 498:
toast.trigger({
message: 'Cannot start the draft when the total lab quota is zero.',
background: 'variant-filled-error',
autohide: false,
});
break;
default:
break;
}
return;
}
};
}}
>
Expand Down
3 changes: 2 additions & 1 deletion app/src/routes/dashboard/(admin)/labs/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export const actions = {
if (!user.is_admin || user.user_id === null || user.lab_id !== null) error(403);

const draft = await db.getActiveDraft();
if (draft !== null && draft.curr_round !== null) error(403);
if (draft !== null && draft.curr_round !== null && draft.curr_round > 0)
error(403, 'It is unsafe to update the lab quota while a draft is ongoing.');

const data = await request.formData();
await db.updateLabQuotas(mapRowTuples(data));
Expand Down
2 changes: 2 additions & 0 deletions app/src/routes/dashboard/(admin)/labs/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
<CreateForm />
<QuotaForm {labs} />
</div>
{:else if draft.curr_round === 0}
<QuotaForm {labs} />
{:else}
{@const { draft_id, active_period_start, curr_round, max_rounds } = draft}
{@const startDate = format(active_period_start, 'PPP')}
Expand Down
2 changes: 1 addition & 1 deletion app/src/routes/dashboard/(draft)/ranks/+page.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export async function load({ locals: { db }, parent }) {
const { user, draft } = await parent();
if (user.is_admin || user.user_id === null || user.lab_id !== null || user.student_number === null) error(403);
const [availableLabs, rankings] = await Promise.all([
db.getAvailableLabs(),
db.getLabRegistry(),
db.getStudentRankings(draft.draft_id, user.email),
]);
return { draft, availableLabs, rankings };
Expand Down
10 changes: 5 additions & 5 deletions app/src/routes/dashboard/(draft)/students/+page.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ export const actions = {
const count = await db.getPendingLabCountInDraft(draft);
if (count > 0) break;

const round = await db.incrementDraftRound(draft);
assert(round !== null);
const incrementDraftRound = await db.incrementDraftRound(draft);
db.logger.info({ incrementDraftRound });
assert(incrementDraftRound !== null, 'The draft to be incremented does not exist.');

const postDraftRoundStartedNotification = await db.postDraftRoundStartedNotification(
draft,
round.curr_round,
incrementDraftRound.curr_round,
);
db.logger.info({ postDraftRoundStartedNotification });

if (round.curr_round === null) break;
db.logger.info({ incrementDraftRound: round });
if (incrementDraftRound.curr_round === null) break;

const autoAcknowledgeLabsWithoutPreferences = await db.autoAcknowledgeLabsWithoutPreferences(draft);
db.logger.info({ autoAcknowledgeLabsWithoutPreferences });
Expand Down
6 changes: 0 additions & 6 deletions database/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,6 @@ export class Database implements Loggable {
return parse(CreatedLab, first).lab_id;
}

@timed async getAvailableLabs() {
const sql = this.#sql;
const labs = await sql`SELECT lab_id, lab_name FROM drap.labs WHERE quota > 0 ORDER BY lab_name`;
return parse(AvailableLabs, labs);
}

@timed async getLabRegistry() {
const sql = this.#sql;
const labs = await sql`SELECT lab_id, lab_name, quota FROM drap.labs ORDER BY lab_name`;
Expand Down
2 changes: 1 addition & 1 deletion postgres/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ CREATE TABLE drap.draft_notifications (
CREATE TABLE drap.user_notifications (
notif_id BIGINT GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY,
lab_id TEXT NOT NULL REFERENCES drap.labs (lab_id),
email TEXT UNIQUE NOT NULL REFERENCES drap.users (email)
email TEXT NOT NULL REFERENCES drap.users (email)
);

0 comments on commit ac3ad9c

Please sign in to comment.