Skip to content

Commit

Permalink
adding metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
cxjohn committed Sep 2, 2023
1 parent a04f1b6 commit 5215781
Show file tree
Hide file tree
Showing 19 changed files with 340 additions and 128 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"react-datetime": "^3.2.0",
"react-dom": "^18.2.0",
"react-draft-wysiwyg": "^1.15.0",
"react-error-boundary": "^4.0.11",
"supabase": "^1.42.7",
"tailwindcss": "^3.2.4",
"trpc": "^0.11.3",
Expand Down
74 changes: 48 additions & 26 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ datasource db {
}

model Evaluation {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String?
status String @default("draft")
description String @default("")
start_time DateTime? @db.Timestamptz(6)
end_time DateTime? @db.Timestamptz(6)
form_description String?
is_upload Boolean?
evaluation_field EvaluationField[]
evaluator Evaluator[]
invitation Invitation[]
submission Submission[]
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String?
status String @default("draft")
description String @default("")
start_time DateTime? @db.Timestamptz(6)
end_time DateTime? @db.Timestamptz(6)
form_description String?
is_upload Boolean?
evaluation_field EvaluationField[]
evaluator Evaluator[]
invitation Invitation[]
submission Submission[]
evaluation_metric EvaluationMetric[]
@@map("evaluation")
}
Expand Down Expand Up @@ -66,19 +67,20 @@ model Invitation {
}

model Submission {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
github_link String?
evaluation_id String @db.Uuid
description Json @db.Json
links Json?
github_handle String?
user_id String? @db.Uuid
is_submitted Boolean @default(false)
contract_id String?
evaluation Evaluation @relation(fields: [evaluation_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
submission_field SubmissionField[]
votes Votes[]
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
github_link String?
evaluation_id String @db.Uuid
description Json @db.Json
links Json?
github_handle String?
user_id String? @db.Uuid
is_submitted Boolean @default(false)
contract_id String?
evaluation Evaluation @relation(fields: [evaluation_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
submission_field SubmissionField[]
votes Votes[]
submission_metric_value SubmissionMetricValue[]
@@map("submission")
}
Expand All @@ -104,7 +106,6 @@ model User {
github_user_id String? @db.Uuid
role String @default("user")
evaluator Evaluator[]
test String?
@@map("user")
}
Expand All @@ -119,3 +120,24 @@ model Votes {
@@id([evaluator_id, submission_id])
@@map("votes")
}

model EvaluationMetric {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
evaluation_id String @db.Uuid
name String
evaluation Evaluation @relation(fields: [evaluation_id], references: [id], onDelete: Cascade)
submission_metric_value SubmissionMetricValue[]
@@map("evaluation_metric")
}

model SubmissionMetricValue {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
evaluation_metric_id String @db.Uuid
value String
submission_id String @db.Uuid
evaluation_metric EvaluationMetric @relation(fields: [evaluation_metric_id], references: [id], onDelete: Cascade)
submission Submission @relation(fields: [submission_id], references: [id], onDelete: Cascade)
@@map("submission_metric_value")
}
69 changes: 68 additions & 1 deletion src/components/admin/Evaluation/EditEvaluation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
import Papa from "papaparse";
import LeftArrow from "public/images/svg/LeftArrow";
import EvaluationSubTitle from "./EvaluationSubTitle";
import Edit from "public/images/svg/Edit";
Expand Down Expand Up @@ -35,6 +36,8 @@ export default function EditEvaluation({ evaluation_id, store }: EditEvaluationP
const [showFormFields, setShowFormFields] = useState<boolean>(false);
const [startDate, setStartDate] = useState(store.evaluation?.start_time ? moment(store.evaluation?.start_time) : "");
const [endDate, setEndDate] = useState(store.evaluation?.end_time ? moment(store.evaluation?.end_time) : "");
const [loading, setLoading] = useState(false);
const [csvData, setCsvData] = useState<Array<Record<string, any>>>([]);

const handleOpenOutcomeModal = (submission: any) => {
setOutcomeModalContent(submission);
Expand Down Expand Up @@ -75,6 +78,26 @@ export default function EditEvaluation({ evaluation_id, store }: EditEvaluationP
store.setEvaluationEndTime(date);
};

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
Papa.parse(file, {
header: true,
complete: (result) => {
setCsvData(result.data as Record<string, any>[]);
},
});
}
};

const handleUploadMetricsClick = async () => {
setLoading(true);

await store.uploadMetrics(csvData);
setCsvData([]);
setLoading(false);
};

useEffect(() => {
setStartDate(store.evaluation?.start_time ? moment(store.evaluation?.start_time) : "");
setEndDate(store.evaluation?.end_time ? moment(store.evaluation?.end_time) : "");
Expand Down Expand Up @@ -279,6 +302,51 @@ export default function EditEvaluation({ evaluation_id, store }: EditEvaluationP
);
})}
</ol>
<hr className="my-10 border-gray" />
<div className="pb-4">
<EvaluationSubTitle text="Metrics (will only work once submissions exist)" />
</div>
<div className="flex justify-between items-center">
<div>
<h5 className="text-offblack font-bold mb-1">Metrics Template</h5>
<button onClick={() => store.getSubmissionsForMetrics(store.evaluation.id)}>
<div className="transition-colors duration-200 ease-in-out transform outline-none focus:outline-none flex flex-row items-center justify-center rounded-md font-bold mx-auto border border-blue bg-blue hover:bg-blue-darkest hover:border-blue-darkest text-white text-sm md:text-base py-1 px-2">
Download
</div>
</button>
</div>
<div>
<div>
<h5 className="text-offblack font-bold mb-1">Upload metrics</h5>
<div className="flex">
<input type="file" accept=".csv" onChange={handleFileChange} />
{csvData.length > 0 ? (
<button
className="transition-colors duration-200 ease-in-out transform outline-none focus:outline-none flex flex-row items-center justify-center rounded-md font-bold border border-blue bg-blue hover:bg-blue-darkest hover:border-blue-darkest text-white text-sm md:text-base py-1 w-20"
onClick={handleUploadMetricsClick}
>
{loading ? "Uploading" : "Upload"}
</button>
) : null}
</div>
</div>
</div>
</div>
<div className="p-4">
{store.evaluation.evaluation_metric &&
store.evaluation.evaluation_metric.map((metric: any) => (
<div key={metric.id} className="mb-4 p-4 border">
<h2 className="font-bold mb-2">{metric.name}</h2>
<ul>
{metric.submission_metric_value.map((submission: any) => (
<li key={submission.id} className="mb-1">
Submission ID: {submission.submission_id} - Value: {submission.value}
</li>
))}
</ul>
</div>
))}
</div>
</div>
<OutcomeModal
store={store}
Expand All @@ -287,7 +355,6 @@ export default function EditEvaluation({ evaluation_id, store }: EditEvaluationP
submission={outcomeModalContent}
evaluation_id={evaluation_id}
/>

<EvaluatorModal
store={store}
open={openEvaluatorModal}
Expand Down
46 changes: 44 additions & 2 deletions src/components/admin/Evaluation/EvaluationStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { v4 as uuid } from "uuid";
import { rpc, Submission } from "src/lib";
import { Submission as submission } from "@prisma/client";
import { trpc } from "src/lib/trpc";
import { parseSubmissionsForMetrics, parseNestedArraysIntoCSV, downloadCSV } from "src/lib/utils";

export interface EvaluationStore {
fetching: boolean;
Expand Down Expand Up @@ -44,6 +45,8 @@ export interface EvaluationStore {
setUserID: (userID: string, id: string) => void;
deleteSubmission: (id: string) => void;
setSubmission: (id: string) => void;
getSubmissionsForMetrics: (evaluation_id: string) => Promise<Error | any>;
uploadMetrics: (csvData: Record<string, any>[], user_id: string) => Promise<void>;
}

export const useEvaluationStore = create<EvaluationStore>()((set, get) => ({
Expand Down Expand Up @@ -413,14 +416,14 @@ export const useEvaluationStore = create<EvaluationStore>()((set, get) => ({
}

try {
const createdEvaluationFields = await trpc().admin.importCSVData.mutate({
const createdEvaluationFields = await trpc().admin.importSubmissionCSVData.mutate({
csvFile: JSON.stringify(csvData),
evaluation_id: evaluation.id,
user_id,
});

if (!createdEvaluationFields) {
console.error(`ERROR -- rpc call importCSVData did not return expected data`);
console.error(`ERROR -- rpc call importSubmissionCSVData did not return expected data`);
}
set({
evaluation: {
Expand Down Expand Up @@ -1134,4 +1137,43 @@ export const useEvaluationStore = create<EvaluationStore>()((set, get) => ({
console.log("ERROR -- rpc call setSubmission failed", err);
});
},
getSubmissionsForMetrics: async (evaluation_id: string): Promise<Error | any> => {
const data = await trpc().admin.getSubmissions.query({ evaluation_id: evaluation_id });

if (!data || data instanceof Error) {
console.error(`ERROR -- rpc call getSubmissions failed. evaluation_id: ${evaluation_id}`, data);
return;
}
const parsedArray = parseSubmissionsForMetrics(data.submissions);
const csv = parseNestedArraysIntoCSV(parsedArray);
const csv_name = `submissions_for_metrics.csv`;
downloadCSV(csv, csv_name);
},
uploadMetrics: async (csvData: Record<string, any>[], user_id: string): Promise<void> => {
const evaluation = get().evaluation;

if (!evaluation) {
console.error("Evaluation is not defined.");
return;
}

try {
const createdEvaluationMetrics = await trpc().admin.importMetricsCSVData.mutate({
csvFile: JSON.stringify(csvData),
evaluation_id: evaluation.id,
});

if (!createdEvaluationMetrics) {
console.error(`ERROR -- rpc call importMetricsCSVData did not return expected data`);
}
set({
evaluation: {
...evaluation,
evaluation_metric: createdEvaluationMetrics,
},
});
} catch (error) {
console.error("Error in mutation call:", error);
}
},
}));
9 changes: 1 addition & 8 deletions src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@ import { EvaluationCard } from "./EvaluationCard";
import { EvaluationEmptyCard } from "./EvaluationEmptyCard";
import { EvaluationItem } from "./EvaluationItem";
import { useDashboardStore } from "./DashboardStore";
import { trpc } from "src/lib/trpc";
import { ErrorBoundary } from "react-error-boundary";

export default function Dashboard() {
const [spinner, setSpinner] = useState(1);

const store = useDashboardStore(setSpinner)();
// const store = useDashboardStore();

console.log(spinner);
const store = useDashboardStore();

useEffect(() => {
store.load();
Expand Down
58 changes: 27 additions & 31 deletions src/components/dashboard/DashboardStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,39 @@ export interface DashboardStore {
createEvaluation: () => Promise<evaluation | Error>;
}

export function useDashboardStore(setSpinner: Dispatch<SetStateAction<number>>) {
return create<DashboardStore>()((set, get) => ({
fetching: true,
evaluations: [],
export const useDashboardStore = create<DashboardStore>()((set, get) => ({
fetching: true,
evaluations: [],

load: async () => {
trpc()
.user.getDashboardStore.query()
.then((data) => {
if (data instanceof Error) {
console.error(`ERROR -- rpc call getUserEvaluations failed`, data);
return;
}
load: async () => {
trpc()
.user.getDashboardStore.query()
.then((data) => {
if (data instanceof Error) {
console.error(`ERROR -- rpc call getUserEvaluations failed`, data);
return;
}

set({
fetching: false,
evaluations: data,
});

setSpinner((prev) => prev + 1);
set({
fetching: false,
evaluations: data,
});
},
});
},

createEvaluation: async (): Promise<evaluation | Error> => {
const newEvaluation: evaluation = {
...Evaluation.init(),
};
createEvaluation: async (): Promise<evaluation | Error> => {
const newEvaluation: evaluation = {
...Evaluation.init(),
};

const res = await trpc().admin.createEvaluation.mutate(newEvaluation);
const res = await trpc().admin.createEvaluation.mutate(newEvaluation);

if (res instanceof Error) {
return res;
}
return newEvaluation;
},
}));
}
if (res instanceof Error) {
return res;
}
return newEvaluation;
},
}));

// setSpinner: Dispatch<SetStateAction<number>>

Expand Down
Loading

0 comments on commit 5215781

Please sign in to comment.