Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 63 additions & 42 deletions src/app/(auth)/grade/_components/statistics-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,40 @@ export interface StatisticsCardProps {
rejectedApplications: number;
deferredApplications: number;
applicationsBegan: number;
regularRoundApplications: number;
totalApplications: number;
}>;
}

export function StatisticsCard(props: StatisticsCardProps) {
const statistics = use(props.statistics);

const acceptedPercentage = (
(statistics.acceptedApplications / statistics.totalApplications) *
100
).toFixed(1);
const rejectedPercentage = (
(statistics.rejectedApplications / statistics.totalApplications) *
100
).toFixed(1);
const deferredPercentage = (
(statistics.deferredApplications / statistics.totalApplications) *
100
).toFixed(1);
const applicationsBeganPercentage = (
(statistics.applicationsBegan / statistics.regularRoundApplications) *
100
).toFixed(1);
const applicationsLeftPercentage = (
((statistics.regularRoundApplications - statistics.applicationsBegan) /
statistics.regularRoundApplications) *
100
).toFixed(1);

return (
<Card>
<CardHeader>
<CardTitle>Priority Applications Progress</CardTitle>
<CardTitle>Applications Progress</CardTitle>
</CardHeader>
<CardContent>
<div className="border-t border-neutral-200">
Expand All @@ -35,16 +58,11 @@ export function StatisticsCard(props: StatisticsCardProps) {
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.acceptedApplications},{" "}
{(
(statistics.acceptedApplications /
statistics.applicationsBegan) *
100
).toFixed(1)}
%
{acceptedPercentage}%
</TooltipTrigger>
<TooltipContent className="font-mono">
accepted_applications /
applications_began
total_applications
</TooltipContent>
</Tooltip>
}
Expand All @@ -55,16 +73,11 @@ export function StatisticsCard(props: StatisticsCardProps) {
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.rejectedApplications},{" "}
{(
(statistics.rejectedApplications /
statistics.applicationsBegan) *
100
).toFixed(1)}
%
{rejectedPercentage}%
</TooltipTrigger>
<TooltipContent className="font-mono">
rejected_applications /
applications_began
total_applications
</TooltipContent>
</Tooltip>
}
Expand All @@ -75,16 +88,11 @@ export function StatisticsCard(props: StatisticsCardProps) {
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.deferredApplications},{" "}
{(
(statistics.deferredApplications /
statistics.applicationsBegan) *
100
).toFixed(1)}
%
{deferredPercentage}%
</TooltipTrigger>
<TooltipContent className="font-mono">
deferred_applications /
applications_began
total_applications
</TooltipContent>
</Tooltip>
}
Expand All @@ -95,15 +103,11 @@ export function StatisticsCard(props: StatisticsCardProps) {
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.applicationsBegan},{" "}
{(
(statistics.applicationsBegan /
statistics.totalApplications) *
100
).toFixed(1)}
%
{applicationsBeganPercentage}%
</TooltipTrigger>
<TooltipContent className="font-mono">
applications_began / total_applications
applications_began /
regular_round_applications
</TooltipContent>
</Tooltip>
}
Expand All @@ -113,27 +117,44 @@ export function StatisticsCard(props: StatisticsCardProps) {
value={
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.totalApplications -
{statistics.regularRoundApplications -
statistics.applicationsBegan}
,{" "}
{(
((statistics.totalApplications -
statistics.applicationsBegan) /
statistics.totalApplications) *
100
).toFixed(1)}
%
, {applicationsLeftPercentage}%
</TooltipTrigger>
<TooltipContent className="font-mono">
(total_applications -
applications_began) / total_applications
(regular_round_applications -
applications_began) /
regular_round_applications
</TooltipContent>
</Tooltip>
}
/>
<Statistic
label="Total Priority Applications"
value={statistics.totalApplications}
label="Regular Round Applications"
value={
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.regularRoundApplications}
</TooltipTrigger>
<TooltipContent className="font-mono">
# of applications submitted during
Regular Round
</TooltipContent>
</Tooltip>
}
/>
<Statistic
label="Total Applications"
value={
<Tooltip>
<TooltipTrigger className="underline underline-offset-2 decoration-1 decoration-dotted decoration-black">
{statistics.totalApplications}
</TooltipTrigger>
<TooltipContent className="font-mono">
Total # of applications submitted
</TooltipContent>
</Tooltip>
}
/>
</dl>
</div>
Expand Down
125 changes: 101 additions & 24 deletions src/app/(auth)/grade/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
"use server";

import { Console, Effect, Option } from "effect";
import { AirtableDb, fetchHackerReviews } from "@/lib/utils/airtable";
import { calculatePriorityStatus } from "@/lib/utils/util";
import type { ApplicationEncoded, ReviewEncoded } from "@/schema/airtable";
import { Console, Effect, Array as EffectArray, Option, Schema } from "effect";
import { AirtableDb } from "@/lib/utils/airtable";
import { calculateStatus } from "@/lib/utils/util";
import {
Application,
type ApplicationEncoded,
ApplicationReviewer1,
ApplicationReviewer2,
ApplicationReviewer3,
Review,
type ReviewEncoded,
} from "@/schema/airtable";

export async function submitApplicationDecision(
applicationId: Pick<ApplicationEncoded, "id">,
Expand All @@ -17,30 +25,78 @@ export async function submitApplicationDecision(
const applicationsTable = db.table("Applications");
const reviewsTable = db.table("Reviews");

const reviews = yield* fetchHackerReviews;
const fetchApplication = Effect.tryPromise(() =>
applicationsTable
.select({
filterByFormula: `{Email} = '${review.email}'`,
maxRecords: 1,
})
.firstPage(),
).pipe(
Effect.flatMap(EffectArray.head),
Effect.flatMap((record) =>
Schema.decodeUnknown(Application)({
id: record.id,
...record.fields,
}),
),
);

const fetchReviews = Effect.tryPromise(() =>
reviewsTable
.select({
filterByFormula: `{Email} = '${review.email}'`,
})
.all(),
).pipe(
Effect.flatMap((records) =>
Effect.allSuccesses(
records.map((record) =>
Schema.decodeUnknown(Review)({
id: record.id,
...record.fields,
}),
),
),
),
);

const [application, reviews] = yield* Effect.all(
[fetchApplication, fetchReviews],
{ concurrency: 2 },
);

const decisions = {
accept: review.decision === "accept" ? 1 : 0,
reject: review.decision === "reject" ? 1 : 0,
accept: 0,
reject: 0,
};
for (const { decision, email } of reviews) {
if (review.email === email) {
if (decision === "accept") {
decisions.accept += 1;
} else if (decision === "reject") {
decisions.reject += 1;
}
}

if (decisions.accept > 2 || decisions.reject > 1) {
return;
for (const { decision } of reviews) {
if (decision === "accept") {
decisions.accept += 1;
} else if (decision === "reject") {
decisions.reject += 1;
}
}

const status = calculatePriorityStatus(decisions);
if (
!application.reviewNeeded ||
decisions.accept >= 1 ||
decisions.reject >= 2
) {
return;
}

const adjustedDecisions = {
accept: decisions.accept + (review.decision === "accept" ? 1 : 0),
reject: decisions.reject + (review.decision === "reject" ? 1 : 0),
};

const status = calculateStatus(adjustedDecisions);
const insertDecision = Option.match(status, {
onNone: () => Effect.void,
onSome: (status) =>
Effect.tryPromise(() =>
onSome: (status) => {
return Effect.tryPromise(() =>
applicationsTable.update([
{
id: applicationId.id,
Expand All @@ -49,7 +105,25 @@ export async function submitApplicationDecision(
},
},
]),
),
);
},
});

const insertReviewer = Effect.tryPromise(() => {
const reviewerSlot =
application.reviewer1 === undefined
? ApplicationReviewer1.literals[0]
: application.reviewer2 === undefined
? ApplicationReviewer2.literals[0]
: ApplicationReviewer3.literals[0];
return applicationsTable.update([
{
id: applicationId.id,
fields: {
[reviewerSlot]: review.reviewer_id,
},
},
]);
});

const insertReview = Effect.tryPromise(() =>
Expand All @@ -65,9 +139,12 @@ export async function submitApplicationDecision(
]),
);

return yield* Effect.all([insertDecision, insertReview], {
concurrency: 2,
});
return yield* Effect.all(
[insertDecision, insertReviewer, insertReview],
{
concurrency: "unbounded",
},
);
}).pipe(
Effect.tapErrorCause((error) => Console.error(error)),
Effect.withSpan("app/(auth)/grade/actions/submitApplicationDecision"),
Expand Down
8 changes: 3 additions & 5 deletions src/app/(auth)/grade/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ import {
} from "@/app/(auth)/grade/_components/statistics-card";
import { createClient } from "@/lib/supabase/server";
import {
findNewHackerApplication,
findHackerApplication,
progressStatistics,
} from "@/lib/utils/airtable";
import { SupabaseUser } from "@/lib/utils/supabase";

export default async function Grade() {
const supabase = await createClient();
const user = SupabaseUser(supabase).pipe(Effect.runPromise);
const application = findNewHackerApplication({ priority: true }).pipe(
const application = findHackerApplication.pipe(
Effect.map(Option.getOrElse(() => null)),
Effect.runPromise,
);
const statistics = progressStatistics({ priority: true }).pipe(
Effect.runPromise,
);
const statistics = progressStatistics.pipe(Effect.runPromise);

return (
<main className="w-full h-full">
Expand Down
Loading