Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
cad8656
Check if lectures have been fetched
nadjajovancevic Aug 5, 2026
cf36ff7
fix: always pass an empty string for className prop
Aug 5, 2026
631347a
fix: remove circular dependency
Aug 5, 2026
872a31e
fix: add/change key prop
Aug 5, 2026
1a75635
fix: remove async/await keywords from mutation handlers
Aug 5, 2026
7e7ff43
chore: use correct operational signs
Aug 5, 2026
5610e25
build: adjust tsconfig to match JupyterLabs
Aug 5, 2026
0636861
fix: show time difference between dates in days, hours and minutes
Aug 6, 2026
31575c1
fix: truncate overflowing text
Aug 6, 2026
432f329
fix: remove width constraint on tooltip's content container
Aug 6, 2026
442843e
fix: center global sidebar buttons when collapsed
Aug 6, 2026
0c19942
fix: adjust icons' styling
Aug 6, 2026
6206047
refactor: separate git and assignment request functions
Aug 7, 2026
89f7d67
refactor: remove error handling from functions in file services
Aug 7, 2026
9f1a082
refactor: add common `baseUrl` variable
Aug 7, 2026
6cdeaf5
refactor: create separate directory for enums
Aug 7, 2026
d3e3113
refactor: change HTTP response validation
Aug 7, 2026
8f388cc
refactor: simplify if-else statements in storage.service.ts
Aug 7, 2026
9003cb6
chore: remove theme connector that included styles of non-existent `.…
Aug 10, 2026
312a507
fix: handle non-JSON response bodies correctly
Aug 10, 2026
4727b07
refactor: remove dead code and rename components to match new design
Aug 10, 2026
adb3659
refactor: create a function that checks if current user has elevated …
Aug 10, 2026
7c70602
fix: display student/instructor view toggle button only when user has…
Aug 10, 2026
7c175f5
WIP: add functions from old labextension for file management
nadjajovancevic Aug 19, 2026
9e93406
fix: use correct function for opening file after grade export
nadjajovancevic Aug 24, 2026
52af2ca
chore: rename const
nadjajovancevic Aug 24, 2026
354989b
fix: use correct baseUrl
nadjajovancevic Aug 24, 2026
8ece9cf
WIP: add submission-related endpoints
nadjajovancevic Aug 24, 2026
bdbda1f
WIP: add grading service
nadjajovancevic Aug 24, 2026
738b220
fix: use correct endpoint uri
nadjajovancevic Aug 24, 2026
2890278
fix: build endpoint uri with correct search params format
nadjajovancevic Aug 24, 2026
e88bbb7
WIP: add gradebook service
nadjajovancevic Aug 24, 2026
f3b362f
WIP: move notebook components into correct dir
nadjajovancevic Aug 24, 2026
bda605f
WIP: add manual grading components
nadjajovancevic Aug 24, 2026
b346b2e
chore: add space to description text
nadjajovancevic Aug 24, 2026
b8f4fe2
chore: use className instead of style attribute
nadjajovancevic Aug 25, 2026
2b2bebc
Merge branch 'feat/new-labextension' into chore/old-code-clean-up
nadjajovancevic Sep 23, 2026
9258910
Refactor time difference text construction
nadjajovancevic Sep 28, 2026
eb6f1b1
Fix URL construction for remote file status request
nadjajovancevic Sep 28, 2026
5f309e7
Simplify URL building with URLSearchParams
nadjajovancevic Sep 28, 2026
1fb7f7e
Refactor makeDirs to use for..of loop
nadjajovancevic Sep 28, 2026
baa7bd3
Fix condition check for elevated permissions
nadjajovancevic Sep 28, 2026
bcb8a77
Update TypeScript lib to include ES2019
nadjajovancevic Sep 28, 2026
833eb87
fix: use reduce function instead of for loops
nadjajovancevic Sep 28, 2026
65645ec
chore: move getSubmissionLogs to submissions.service.ts
nadjajovancevic Sep 29, 2026
0e274d8
fix: move getStudentSubmissions to submissions.service.ts and impleme…
nadjajovancevic Sep 29, 2026
8f141d3
fix: remove functions that used non-existent endpoints
nadjajovancevic Sep 29, 2026
7cef3dd
fix: update comments to reflect endpoint paths
nadjajovancevic Sep 29, 2026
cf60ced
chore: move pullFeedback function to grading.service.ts
nadjajovancevic Sep 29, 2026
70e9269
chore: rename function to reflect its purpose
nadjajovancevic Sep 29, 2026
3e6eaba
chore: remove getFeedback function that called non-existent endpoint
nadjajovancevic Sep 29, 2026
3fefa8a
fix: initialize theme before connecting it to themeManager
nadjajovancevic Sep 30, 2026
a5825bc
fix: merge duplicate conditions
nadjajovancevic Sep 30, 2026
a3dd1a7
refactor: simplify and optimize old code
nadjajovancevic Sep 30, 2026
95623e8
Refactor submissions API path and add authentication
nadjajovancevic Oct 1, 2026
175dd1f
Update Switch component to use onCheckedChange
nadjajovancevic Oct 1, 2026
9dc69cb
Fix setTimeDiff function call syntax
nadjajovancevic Oct 1, 2026
ba990ac
Fix typo in getMaxPoints method for notebooks
nadjajovancevic Oct 1, 2026
cb48d3d
fix: fix typo in getExtraCredits function
nadjajovancevic Oct 1, 2026
d27831c
Merge branch 'chore/old-code-clean-up' of github.com:TU-Wien-dataLAB/…
nadjajovancevic Oct 1, 2026
9e63429
Handle undefined permissions in hasElevatedPermissions
nadjajovancevic Oct 1, 2026
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
8 changes: 4 additions & 4 deletions packages/labextension/grader_labextension/handlers/grading.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def put(self, lecture_id: int, assignment_id: int):
class GradingAutoHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to
/lectures/{lecture_id}/assignments/{assignment_id}/submissions/{submission_id}/auto.
/lectures/{lecture_id}/assignments/{assignment_id}/grading/{submission_id}/auto.
"""

@authenticated
Expand Down Expand Up @@ -124,7 +124,7 @@ async def get(self, lecture_id: int, assignment_id: int, sub_id: int):
class GradingManualHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to
/lectures/{lecture_id}/assignments/{assignment_id}/submissions/{submission_id}/manual.
/lectures/{lecture_id}/assignments/{assignment_id}/grading/{submission_id}/manual.
"""

@authenticated
Expand Down Expand Up @@ -215,7 +215,7 @@ async def get(self, lecture_id: int, assignment_id: int, sub_id: int):
class GenerateFeedbackHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to
/lectures/{lecture_id}/assignments/{assignment_id}/submissions/{submission_id}/feedback.
/lectures/{lecture_id}/assignments/{assignment_id}/grading/{submission_id}/feedback.
"""

@authenticated
Expand Down Expand Up @@ -248,7 +248,7 @@ async def get(self, lecture_id: int, assignment_id: int, sub_id: int):
class PullFeedbackHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to
/lectures/{lecture_id}/assignments/{assignment_id}/submissions/{submission_id}/pull/feedback.
/lectures/{lecture_id}/assignments/{assignment_id}/grading/{submission_id}/pull/feedback.
"""

@authenticated
Expand Down
28 changes: 28 additions & 0 deletions packages/labextension/grader_labextension/handlers/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,31 @@ async def get(self, lecture_id: int, assignment_id: int):
self.log.error(e)
raise HTTPError(e.code, reason=e.message)
self.write(json.dumps(response))


@register_handler(path=r"api\/users\/(?P<username>[^/]+)\/submissions\/?")
class SubmissionUserHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to /users/{username}/submissions.
"""

@authenticated
async def get(self, username: str):
Comment thread
nadjajovancevic marked this conversation as resolved.
"""Return the submissions of a specific user.

:param username: username of the user
:type username: str
"""

try:
response = await self.request_service.request(
method="GET",
endpoint=f"{self.service_base_url}api/users/{username}/submissions",
header=self.grader_authentication_header,
response_callback=self.set_service_headers,
)
self.log.info(f"{response}")
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)
self.write(json.dumps(response))
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const AssignmentCard = (props: IAssignment) => {
</CardContent>
<CardFooter
className={
'items-start flex-col justify-end gap-4 self-stretch bg-[#EBEBEB] p-4'
'items-start flex-col justify-end gap-4 self-stretch bg-[#EBEBEB] dark:bg-[#2B2B2B] p-4'
}
>
<Button className={'w-full cursor-pointer'}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,7 @@ import {
LATE_SUBMISSIONS,
WHITELIST_FILE_PATTERNS
} from './static/assignment-metadata-explainations';
import {
calculateDaysDifference,
determineDisplayText,
buildPeriod
} from '../../utils/utils';
import { pluralize, buildPeriod } from '../../utils/utils';
import moment from 'moment';
import { useGroups } from '../../../pages/instructor-view/lecture';
import { Checkbox } from '../../../shadcn-components/ui/checkbox';
Expand Down Expand Up @@ -136,6 +132,9 @@ export const AssignmentCreateEditDialog = (props: IAssignmentSettingsForm) => {
const [deadlineOpen, setDeadlineOpen] = React.useState<boolean>(false);
const [createAnother, setCreateAnother] = useState(false);
const [whitelistPatternsInput, setWhitelistPatternsInput] = useState('');
const [timeDiff, setTimeDiff] = useState<Record<string, number> | undefined>(
undefined
);
const handleAddAllowedFilePattern = (field: AnyFieldApi) => {
if (
whitelistPatternsInput.trim().length > 0 &&
Expand Down Expand Up @@ -170,6 +169,32 @@ export const AssignmentCreateEditDialog = (props: IAssignmentSettingsForm) => {
addCustomGroup(label);
}

const timeDiffText = () => {
if (!timeDiff) {
return '';
}
const daysDiff = timeDiff.daysDiff;
const hoursDiff = timeDiff.hoursDiff;
const minutesDiff = timeDiff.minutesDiff;
const parts: string[] = [];
if (daysDiff > 0) {
parts.push(
`${daysDiff} ${pluralize({ text: 'day', data: daysDiff })}`
);
}
if (hoursDiff > 0) {
parts.push(
`${hoursDiff} ${pluralize({ text: 'hour', data: hoursDiff })}`
);
}
if (minutesDiff > 0) {
parts.push(
`${minutesDiff} ${pluralize({ text: 'minute', data: minutesDiff })}`
);
}
return parts.length > 0 ? parts.join(', ') : 'less than a minute';
};

return (
<Dialog open={props.openDialog} onOpenChange={props.setOpenDialog}>
<DialogContent className={'overflow-y-auto'}>
Expand Down Expand Up @@ -300,26 +325,19 @@ export const AssignmentCreateEditDialog = (props: IAssignmentSettingsForm) => {
}
}}
children={field => {
let dayDiff;
return (
<div className={'flex flex-col w-full'}>
<DatePickerTime
open={deadlineOpen}
setOpen={setDeadlineOpen}
field={field}
setTimeDiff={setTimeDiff}
/>
{field.state.value &&
field.state.meta.isTouched &&
field.state.meta.isValid && (
<p className={'text-base text-green-700 italic'}>
Deadline is in{' '}
{
(dayDiff = calculateDaysDifference({
startDate: new Date(field.state.value),
endDate: new Date()
}))
}{' '}
{determineDisplayText({ text: 'day', data: dayDiff })}
Deadline is in {timeDiffText()}.
</p>
)}
{!field.state.meta.isValid && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import {
SelectValue
} from '../../../shadcn-components/ui/select';
import { Button } from '../../../shadcn-components/ui/button';
import { exportGrades } from '../../../../services/lectures.service';
import { lectureBasePath, openFile } from '../../../../services/file.service';
import { exportGrades } from '../../../../services/submissions.service';
import {
lectureBasePath,
openInFileBrowser,
openInNewTab
} from '../../../../services/local-file.service';
import { Lecture } from '../../../../model/lecture';
import { goToPath } from '../../../../services/file-browser.service';
import { useMutationStatus } from '../../../../widget';

interface IExportGradesDialogProps {
lecture: Lecture;
Expand All @@ -29,18 +33,20 @@ interface IExportGradesDialogProps {
export const ExportGradesDialog = (props: IExportGradesDialogProps) => {
const [format, setFormat] = React.useState<'csv' | 'json'>('csv');
const [filter, setFilter] = React.useState<'best' | 'latest'>('best');
const { setStatus } = useMutationStatus();

const handleExport = async () => {
try {
await exportGrades(props.lecture.id, filter, format);
// open file in new tab
await openFile(
await openInNewTab(
`${lectureBasePath}${props.lecture.code}/${props.lecture.name}_${filter}_submissions.${format}`
);
// go into correct directory
await goToPath(`${lectureBasePath}${props.lecture.code}`);
await openInFileBrowser(`${lectureBasePath}${props.lecture.code}`);
} catch (error: any) {
console.error('Error exporting grades:', error);
setStatus({
status: 'error',
message: error?.message || 'Failed to export grades'
});
} finally {
props.setIsOpen(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const EditLectureDialog = (props: IEditLectureDialog) => {
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
<Tooltip>
<DialogTrigger render={<TooltipTrigger />}>
<Pencil className={'size-4 fill-primary text-primary!'} />
<Pencil className={'size-5 fill-primary text-card!'} />
</DialogTrigger>
<TooltipContent>
<p>Edit course</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { Link } from 'react-router';
import React from 'react';
import { Lecture } from '../../../../model/lecture';
import { useQuery } from '@tanstack/react-query';
import { getUsers } from '../../../../services/lectures.service';
import { getLectureUsers } from '../../../../services/lectures.service';
import { getAllAssignments } from '../../../../services/assignments.service';
import { IconTextInfo } from '../../ui/icon-text-info';
import { determineDisplayText, highlightText } from '../../utils/utils';
import { pluralize, highlightText } from '../../utils/utils';
import { User } from '../../../../model/user';
import { EditLectureDialog } from './edit-lecture-dialog';
import {
Expand All @@ -32,7 +32,7 @@ export const LectureCard = (props: ILectureCard) => {
const lecture = props.lecture;
const { data: users, isPending: isPendingUsers } = useQuery({
queryKey: ['users', lecture.id],
queryFn: async () => getUsers(lecture.id, false)
queryFn: async () => getLectureUsers(lecture.id, false)
});

const { data: assignments, isPending: isPendingAssignments } = useQuery({
Expand Down Expand Up @@ -80,10 +80,10 @@ export const LectureCard = (props: ILectureCard) => {
<IconTextInfo
icon={
<FileText
className={'fill-foreground size-5 text-white! self-center'}
className={'fill-foreground size-5 text-card! self-center'}
/>
}
text={`${assignments.length.toString()} ${determineDisplayText({
text={`${assignments.length.toString()} ${pluralize({
text: 'Assignment',
data: assignments
})}`}
Expand All @@ -92,10 +92,10 @@ export const LectureCard = (props: ILectureCard) => {
<IconTextInfo
icon={
<UserIcon
className={'fill-foreground text-foreground! size-5 self-center'}
className={'fill-foreground text-card! size-5 self-center'}
/>
}
text={`${students.length.toString()} ${determineDisplayText({
text={`${students.length.toString()} ${pluralize({
text: 'Student',
data: students
})}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FileText, UserIcon } from 'lucide-react';
import { Link } from 'react-router';
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { getUsers } from '../../../../services/lectures.service';
import { getLectureUsers } from '../../../../services/lectures.service';
import { getAllAssignments } from '../../../../services/assignments.service';
import { IconTextInfo } from '../../ui/icon-text-info';
import { User } from '../../../../model/user';
Expand All @@ -27,7 +27,7 @@ export const LectureRow = (props: ILectureRow) => {

const { data: users, isPending: isPendingUsers } = useQuery({
queryKey: ['users', lecture.id],
queryFn: async () => getUsers(lecture.id, false)
queryFn: async () => getLectureUsers(lecture.id, false)
});

const { data: assignments, isPending: isPendingAssignments } = useQuery({
Expand Down
Loading
Loading