Skip to content

Commit

Permalink
uses display name instead of uid (#328)
Browse files Browse the repository at this point in the history
* uses display name instead of uid

* re added authentication

* fix failing test

* changed APP var

* removed console log

* replaced path and merge conflicts

* removed 404 for submissions

* fixed page spamming backend with requests

* type error

---------

Co-authored-by: Aron Buzogany <[email protected]>
  • Loading branch information
Gerwoud and AronBuzogany authored May 9, 2024
1 parent 2822c1b commit 4bfa40f
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ def get_last_submissions_per_user(project_id):
(Submission.submission_time == latest_submissions.c.max_time)
).all()

if not submissions:
return {"message": "No submissions found", "url": BASE_URL}, 404

return {"message": "Resource fetched succesfully", "data": submissions}, 200

class SubmissionDownload(Resource):
Expand Down
4 changes: 4 additions & 0 deletions backend/project/endpoints/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def get(self):
role = Role[role.upper()]
query = query.filter(userModel.role == role)

uid = request.args.getlist("uid")
if len(uid) > 0:
query = query.filter(userModel.uid.in_(uid))

users = query.all()
users = [user.to_dict() for user in users]

Expand Down
10 changes: 6 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ProjectOverView from "./pages/project/projectOverview.tsx";
import { synchronizeJoinCode } from "./loaders/join-code.ts";
import { fetchMe } from "./utils/fetches/FetchMe.ts";
import {fetchProjectForm} from "./components/ProjectForm/project-form.ts";
import loadSubmissionOverview from "./loaders/submission-overview-loader.ts";

const router = createBrowserRouter(
createRoutesFromElements(
Expand All @@ -34,10 +35,6 @@ const router = createBrowserRouter(
<Route index element={<HomePages />} loader={fetchProjectPage} />
<Route path=":lang" element={<LanguagePath />}>
<Route path="home" element={<HomePages />} loader={fetchProjectPage} />
<Route
path="project/:projectId/overview"
element={<SubmissionsOverview />}
/>
<Route path="courses">
<Route index element={<AllCoursesTeacher />} loader={dataLoaderCourses}/>
<Route path="join" loader={synchronizeJoinCode} />
Expand All @@ -49,6 +46,11 @@ const router = createBrowserRouter(
element={<ProjectOverView />}
loader={fetchProjectPage}
/>
<Route
loader={loadSubmissionOverview}
path=":projectId/overview"
element={<SubmissionsOverview />}
/>
<Route path=":projectId" element={<ProjectView />}></Route>
<Route path="create" element={<ProjectCreateHome />} loader={fetchProjectForm}/>
</Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
import {Box, Button, Typography} from "@mui/material";
import {useEffect, useState} from "react";
import {useParams} from "react-router-dom";
import { Box, Button, Typography } from "@mui/material";
import { useLoaderData, useParams } from "react-router-dom";
import ProjectSubmissionsOverviewDatagrid from "./ProjectSubmissionOverviewDatagrid.tsx";
import download from 'downloadjs';
import {useTranslation} from "react-i18next";
import download from "downloadjs";
import { useTranslation } from "react-i18next";
import { authenticatedFetch } from "../../utils/authenticated-fetch.ts";
const apiUrl = import.meta.env.VITE_APP_API_HOST;
import { Project } from "../Courses/CourseUtils.tsx";
import { Submission } from "../../types/submission.ts";

const APIURL = import.meta.env.VITE_APP_API_HOST;

/**
* @returns Overview page for submissions
*/
export default function ProjectSubmissionOverview() {

const { t } = useTranslation('submissionOverview', { keyPrefix: 'submissionOverview' });

useEffect(() => {
fetchProject();
const { t } = useTranslation("submissionOverview", {
keyPrefix: "submissionOverview",
});

const fetchProject = async () => {
const response = await authenticatedFetch(`${apiUrl}/projects/${projectId}`)
const jsonData = await response.json();
setProjectTitle(jsonData["data"].title);

}
const { projectId } = useParams<{ projectId: string }>();
const { projectData, submissionsWithUsers } = useLoaderData() as {
projectData: Project;
submissionsWithUsers: Submission[];
};

const downloadProjectSubmissions = async () => {
await authenticatedFetch(`${apiUrl}/projects/${projectId}/submissions-download`)
.then(res => {
await authenticatedFetch(
`${APIURL}/projects/${projectId}/submissions-download`
)
.then((res) => {
return res.blob();
})
.then(blob => {
download(blob, 'submissions.zip');
.then((blob) => {
download(blob, "submissions.zip");
});
}

const [projectTitle, setProjectTitle] = useState<string>("")
const { projectId } = useParams<{ projectId: string }>();
};

return (
<Box
Expand All @@ -47,10 +44,16 @@ export default function ProjectSubmissionOverview() {
paddingTop="50px"
>
<Box width="40%">
<Typography minWidth="440px" variant="h6" align="left">{projectTitle}</Typography>
<ProjectSubmissionsOverviewDatagrid />
<Typography minWidth="440px" variant="h6" align="left">
{projectData["title"]}
</Typography>
<ProjectSubmissionsOverviewDatagrid
submissions={submissionsWithUsers}
/>
</Box>
<Button onClick={downloadProjectSubmissions} variant="contained">{t("downloadButton")}</Button>
<Button onClick={downloadProjectSubmissions} variant="contained">
{t("downloadButton")}
</Button>
</Box>
)
}
);
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import {useParams} from "react-router-dom";
import {useEffect, useState} from "react";
import {DataGrid, GridColDef, GridRenderCellParams} from "@mui/x-data-grid";
import {Box, IconButton} from "@mui/material";
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { green, red } from '@mui/material/colors';
import CancelIcon from '@mui/icons-material/Cancel';
import DownloadIcon from '@mui/icons-material/Download';
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
import { Box, IconButton } from "@mui/material";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { green, red } from "@mui/material/colors";
import CancelIcon from "@mui/icons-material/Cancel";
import DownloadIcon from "@mui/icons-material/Download";
import download from "downloadjs";
import { authenticatedFetch } from "../../utils/authenticated-fetch";
import { Submission } from "../../types/submission";

const apiUrl = import.meta.env.VITE_APP_API_HOST;

interface Submission {
grading: string;
project_id: string;
submission_id: string;
submission_path: string;
submission_status: string;
submission_time: string;
uid: string;
}
const APIURL = import.meta.env.VITE_APP_API_HOST;

/**
* @returns unique id for datarows
Expand All @@ -29,68 +18,60 @@ function getRowId(row: Submission) {
}

const fetchSubmissionsFromUser = async (submission_id: string) => {
await authenticatedFetch(`${apiUrl}/submissions/${submission_id}/download`)
.then(res => {
await authenticatedFetch(`${APIURL}/submissions/${submission_id}/download`)
.then((res) => {
return res.blob();
})
.then(blob => {
.then((blob) => {
download(blob, `submissions_${submission_id}.zip`);
});
}
};

const columns: GridColDef<Submission>[] = [
{ field: 'submission_id', headerName: 'Submission ID', flex: 0.4 },
{ field: 'uid', headerName: 'Student ID', width: 160, flex: 0.4 },
{ field: "submission_id", headerName: "Submission ID", flex: 0.4 },
{ field: "display_name", headerName: "Student", width: 160, flex: 0.4 },
{
field: 'grading',
headerName: 'Grading',
field: "grading",
headerName: "Grading",
editable: true,
flex: 0.2
flex: 0.2,
},
{
field: 'submission_status',
headerName: 'Status',
field: "submission_status",
headerName: "Status",
renderCell: (params: GridRenderCellParams<Submission>) => (
<>
{
params.row.submission_status === "SUCCESS" ? (
<CheckCircleIcon sx={{ color: green[500] }} />
) : <CancelIcon sx={{ color: red[500] }}/>
}
{params.row.submission_status === "SUCCESS" ? (
<CheckCircleIcon sx={{ color: green[500] }} />
) : (
<CancelIcon sx={{ color: red[500] }} />
)}
</>
)
),
},
{
field: 'submission_path',
headerName: 'Download',
field: "submission_path",
headerName: "Download",
renderCell: (params: GridRenderCellParams<Submission>) => (
<IconButton onClick={() => fetchSubmissionsFromUser(params.row.submission_id)}>
<IconButton
onClick={() => fetchSubmissionsFromUser(params.row.submission_id)}
>
<DownloadIcon />
</IconButton>
)
}];
),
},
];

/**
* @returns the datagrid for displaying submissiosn
*/
export default function ProjectSubmissionsOverviewDatagrid() {
const { projectId } = useParams<{ projectId: string }>();
const [submissions, setSubmissions] = useState<Submission[]>([])

useEffect(() => {
fetchLastSubmissionsByUser();
});

const fetchLastSubmissionsByUser = async () => {
const response = await authenticatedFetch(`${apiUrl}/projects/${projectId}/latest-per-user`)
const jsonData = await response.json();
setSubmissions(jsonData.data);
}

export default function ProjectSubmissionsOverviewDatagrid({
submissions,
}: {
submissions: Submission[];
}) {
return (
<Box
my={4}
>
<Box my={4}>
<DataGrid
getRowId={getRowId}
rows={submissions}
Expand All @@ -99,5 +80,5 @@ export default function ProjectSubmissionsOverviewDatagrid() {
disableRowSelectionOnClick
/>
</Box>
)
}
);
}
57 changes: 57 additions & 0 deletions frontend/src/loaders/submission-overview-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Params } from "react-router-dom";
import { Me } from "../types/me";
import { Submission } from "../types/submission";
import { authenticatedFetch } from "../utils/authenticated-fetch";

const APIURL = import.meta.env.VITE_APP_API_HOST;

const fetchDisplaynameByUid = async (uids: [string]) => {
const uidParams = new URLSearchParams();
for (const uid of uids) {
uidParams.append("uid", uid);
}
const uidUrl = `${APIURL}/users?` + uidParams;
const response = await authenticatedFetch(uidUrl);
const jsonData = await response.json();

return jsonData.data;
};

/**
*
* @param param0 - projectId
* @returns - projectData and submissionsWithUsers
*/
export default async function loadSubmissionOverview({
params,
}: {
params: Params<string>;
}) {
const projectId = params.projectId;
const projectResponse = await authenticatedFetch(
`${APIURL}/projects/${projectId}`
);
const projectData = (await projectResponse.json())["data"];

const overviewResponse = await authenticatedFetch(
`${APIURL}/projects/${projectId}/latest-per-user`
);
const jsonData = await overviewResponse.json();
const uids = jsonData.data.map((submission: Submission) => submission.uid);
const users = await fetchDisplaynameByUid(uids);

const submissionsWithUsers = jsonData.data.map((submission: Submission) => {
// Find the corresponding user for this submission's UID
const user = users.find((user: Me) => user.uid === submission.uid);
// Add user information to the submission
return {
...submission,
display_name: user.display_name,
};
});

return {
projectData,
submissionsWithUsers,
};
}
1 change: 1 addition & 0 deletions frontend/src/types/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface Submission {
submission_id: string;
submission_time: string;
submission_status: string;
uid: string;
}

0 comments on commit 4bfa40f

Please sign in to comment.