more layout and cleanup

This commit is contained in:
2024-09-19 22:00:07 -06:00
parent ab5dbed383
commit 6ff7ca6469
12 changed files with 347 additions and 255 deletions

View File

@@ -1,5 +1,9 @@
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import CourseContextProvider from "./context/CourseContextProvider"; import CourseContextProvider from "./context/CourseContextProvider";
import { Suspense } from "react"; import { Suspense } from "react";
import { getQueryClient } from "@/app/providersQueryClientUtils";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { hydrateCanvasCourse } from "@/hooks/hookHydration";
export default async function CourseLayout({ export default async function CourseLayout({
children, children,
@@ -13,11 +17,19 @@ export default async function CourseLayout({
console.log("cannot load course that is .js.map " + decodedCourseName); console.log("cannot load course that is .js.map " + decodedCourseName);
return <div></div>; return <div></div>;
} }
const settings = await fileStorageService.settings.getCourseSettings(
decodedCourseName
);
const queryClient = getQueryClient();
await hydrateCanvasCourse(settings.canvasId, queryClient);
const dehydratedState = dehydrate(queryClient);
return ( return (
<Suspense> <Suspense>
<CourseContextProvider localCourseName={decodedCourseName}> <HydrationBoundary state={dehydratedState}>
{children} <CourseContextProvider localCourseName={decodedCourseName}>
</CourseContextProvider> {children}
</CourseContextProvider>
</HydrationBoundary>
</Suspense> </Suspense>
); );
} }

View File

@@ -8,7 +8,7 @@ export default function AssignmentPreview({
assignment: LocalAssignment; assignment: LocalAssignment;
}) { }) {
return ( return (
<div> <div className="h-full overflow-y-auto">
<section> <section>
<div className="flex"> <div className="flex">
<div className="flex-1 text-end pe-3">Due Date</div> <div className="flex-1 text-end pe-3">Due Date</div>

View File

@@ -21,6 +21,8 @@ import { Spinner } from "@/components/Spinner";
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils"; import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
import ClientOnly from "@/components/ClientOnly"; import ClientOnly from "@/components/ClientOnly";
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling"; import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
import { LocalCourseSettings } from "@/models/local/localCourse";
export default function EditAssignment({ export default function EditAssignment({
moduleName, moduleName,
@@ -29,6 +31,7 @@ export default function EditAssignment({
assignmentName: string; assignmentName: string;
moduleName: string; moduleName: string;
}) { }) {
const { data: settings } = useLocalCourseSettingsQuery();
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName); const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
const updateAssignment = useUpdateAssignmentMutation(); const updateAssignment = useUpdateAssignmentMutation();
@@ -36,6 +39,7 @@ export default function EditAssignment({
localAssignmentMarkdown.toMarkdown(assignment) localAssignmentMarkdown.toMarkdown(assignment)
); );
const [error, setError] = useState(""); const [error, setError] = useState("");
const [showHelp, setShowHelp] = useState(false);
useEffect(() => { useEffect(() => {
const delay = 500; const delay = 500;
@@ -72,14 +76,23 @@ export default function EditAssignment({
]); ]);
return ( return (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col align-middle px-1">
<div className="columns-2 min-h-0 flex-1"> <div className={"min-h-96 flex flex-row w-full"}>
{showHelp && (
<pre className=" max-w-96">
<code>{getHelpString(settings)}</code>
</pre>
)}
<div className="flex-1 h-full"> <div className="flex-1 h-full">
<MonacoEditor value={assignmentText} onChange={setAssignmentText} /> <MonacoEditor value={assignmentText} onChange={setAssignmentText} />
</div> </div>
<div className="h-full"> <div className="flex-1 h-full">
<div className="text-red-300">{error && error}</div> <div className="text-red-300">{error && error}</div>
<div className="px-3 h-full">
<AssignmentPreview assignment={assignment} /> <AssignmentPreview assignment={assignment} />
</div>
</div> </div>
</div> </div>
<ClientOnly> <ClientOnly>
@@ -87,6 +100,7 @@ export default function EditAssignment({
<AssignmentButtons <AssignmentButtons
moduleName={moduleName} moduleName={moduleName}
assignmentName={assignmentName} assignmentName={assignmentName}
toggleHelp={() => setShowHelp((h) => !h)}
/> />
</SuspenseAndErrorHandling> </SuspenseAndErrorHandling>
</ClientOnly> </ClientOnly>
@@ -94,12 +108,30 @@ export default function EditAssignment({
); );
} }
function getHelpString(settings: LocalCourseSettings) {
const groupNames = settings.assignmentGroups.map((g) => g.name).join("\n- ");
const helpString = `SubmissionTypes:
- ${AssignmentSubmissionType.ONLINE_TEXT_ENTRY}
- ${AssignmentSubmissionType.ONLINE_UPLOAD}
- ${AssignmentSubmissionType.DISCUSSION_TOPIC}
AllowedFileUploadExtensions:
- pdf
- jpg
- jpeg
- png
Assignment Group Names:
- ${groupNames}`;
return helpString;
}
function AssignmentButtons({ function AssignmentButtons({
moduleName, moduleName,
assignmentName, assignmentName,
toggleHelp,
}: { }: {
assignmentName: string; assignmentName: string;
moduleName: string; moduleName: string;
toggleHelp: () => void;
}) { }) {
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
@@ -113,61 +145,66 @@ function AssignmentButtons({
(a) => a.name === assignmentName (a) => a.name === assignmentName
); );
return ( return (
<div className="p-5 flex flex-row justify-end gap-3"> <div className="p-5 flex flex-row justify-between gap-3">
{(addToCanvas.isPending || <div>
deleteFromCanvas.isPending || <button onClick={toggleHelp}>Toggle Help</button>
updateAssignment.isPending) && <Spinner />} </div>
{assignmentInCanvas && !assignmentInCanvas.published && ( <div className="flex flex-row gap-3 justify-end">
<div className="text-rose-300 my-auto">Not Published</div> {(addToCanvas.isPending ||
)} deleteFromCanvas.isPending ||
{!assignmentInCanvas && ( updateAssignment.isPending) && <Spinner />}
<button {assignmentInCanvas && !assignmentInCanvas.published && (
disabled={addToCanvas.isPending} <div className="text-rose-300 my-auto">Not Published</div>
onClick={() => addToCanvas.mutate(assignment)} )}
> {!assignmentInCanvas && (
Add to canvas <button
</button> disabled={addToCanvas.isPending}
)} onClick={() => addToCanvas.mutate(assignment)}
{assignmentInCanvas && ( >
<a Add to canvas
className="btn" </button>
target="_blank" )}
href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`} {assignmentInCanvas && (
> <a
View in Canvas className="btn"
</a> target="_blank"
)} href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`}
{assignmentInCanvas && ( >
<button View in Canvas
className="" </a>
disabled={deleteFromCanvas.isPending} )}
onClick={() => {assignmentInCanvas && (
updateAssignment.mutate({ <button
canvasAssignmentId: assignmentInCanvas.id, className=""
assignment, disabled={deleteFromCanvas.isPending}
}) onClick={() =>
} updateAssignment.mutate({
> canvasAssignmentId: assignmentInCanvas.id,
Update in Canvas assignment,
</button> })
)} }
{assignmentInCanvas && ( >
<button Update in Canvas
className="btn-danger" </button>
disabled={deleteFromCanvas.isPending} )}
onClick={() => {assignmentInCanvas && (
deleteFromCanvas.mutate({ <button
canvasAssignmentId: assignmentInCanvas.id, className="btn-danger"
assignmentName: assignment.name, disabled={deleteFromCanvas.isPending}
}) onClick={() =>
} deleteFromCanvas.mutate({
> canvasAssignmentId: assignmentInCanvas.id,
Delete from Canvas assignmentName: assignment.name,
</button> })
)} }
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}> >
Go Back Delete from Canvas
</Link> </button>
)}
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Go Back
</Link>
</div>
</div> </div>
); );
} }

View File

@@ -23,6 +23,10 @@
} }
.monaco-editor { position: absolute !important; } .monaco-editor { position: absolute !important; }
.monaco-editor .mtk1 {
@apply text-slate-300;
}
h1 { h1 {
@apply text-4xl font-bold my-1; @apply text-4xl font-bold my-1;
} }

View File

@@ -6,7 +6,6 @@ import { getQueryClient } from "./providersQueryClientUtils";
import { hydrateCourses } from "@/hooks/hookHydration"; import { hydrateCourses } from "@/hooks/hookHydration";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { MyToaster } from "./MyToaster"; import { MyToaster } from "./MyToaster";
import "katex/dist/katex.min.css";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Canvas Manager 2.0", title: "Canvas Manager 2.0",

View File

@@ -12,8 +12,8 @@ import { LocalAssignment } from "@/models/local/assignment/localAssignment";
export const canvasAssignmentKeys = { export const canvasAssignmentKeys = {
assignments: (canvasCourseId: number) => assignments: (canvasCourseId: number) =>
["canvas", canvasCourseId, "assignments"] as const, ["canvas", canvasCourseId, "assignments"] as const,
assignment: (canvasCourseId: number, assignmentName: string) => // assignment: (canvasCourseId: number, assignmentName: string) =>
["canvas", canvasCourseId, "assignment", assignmentName] as const, // ["canvas", canvasCourseId, "assignment", assignmentName] as const,
}; };
export const useCanvasAssignmentsQuery = () => { export const useCanvasAssignmentsQuery = () => {

View File

@@ -1,6 +1,5 @@
import { canvasService } from "@/services/canvas/canvasService"; import { canvasService } from "@/services/canvas/canvasService";
import { QueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import { canvasCourseModuleKeys } from "./canvasModuleHooks";
export const canvasKeys = { export const canvasKeys = {
allTerms: ["all canvas terms"] as const, allTerms: ["all canvas terms"] as const,

View File

@@ -2,10 +2,18 @@ import { QueryClient } from "@tanstack/react-query";
import { localCourseKeys } from "./localCourse/localCourseKeys"; import { localCourseKeys } from "./localCourse/localCourseKeys";
import { fileStorageService } from "@/services/fileStorage/fileStorageService"; import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import { LocalCourseSettings } from "@/models/local/localCourse"; import { LocalCourseSettings } from "@/models/local/localCourse";
import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService";
import { canvasAssignmentKeys } from "./canvas/canvasAssignmentHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
import { LocalQuiz } from "@/models/local/quiz/localQuiz";
import { canvasQuizService } from "@/services/canvas/canvasQuizService";
import { canvasPageService } from "@/services/canvas/canvasPageService";
import { canvasQuizKeys } from "./canvas/canvasQuizHooks";
import { canvasPageKeys } from "./canvas/canvasPageHooks";
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr // https://tanstack.com/query/latest/docs/framework/react/guides/ssr
export const hydrateCourses = async (queryClient: QueryClient) => { export const hydrateCourses = async (queryClient: QueryClient) => {
const allSettings = await fileStorageService.settings.getAllCoursesSettings(); const allSettings = await fileStorageService.settings.getAllCoursesSettings();
const courseNames = allSettings.map((s) => s.name);
await queryClient.prefetchQuery({ await queryClient.prefetchQuery({
queryKey: localCourseKeys.allCoursesSettings, queryKey: localCourseKeys.allCoursesSettings,
queryFn: () => allSettings, queryFn: () => allSettings,
@@ -27,51 +35,7 @@ export const hydrateCourse = async (
courseName courseName
); );
const modulesData = await Promise.all( const modulesData = await Promise.all(
moduleNames.map(async (moduleName) => { moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName))
const [assignmentNames, pageNames, quizNames] = await Promise.all([
await fileStorageService.assignments.getAssignmentNames(
courseName,
moduleName
),
await fileStorageService.pages.getPageNames(courseName, moduleName),
await fileStorageService.quizzes.getQuizNames(courseName, moduleName),
]);
const [assignments, quizzes, pages] = await Promise.all([
await Promise.all(
assignmentNames.map(
async (assignmentName) =>
await fileStorageService.assignments.getAssignment(
courseName,
moduleName,
assignmentName
)
)
),
await Promise.all(
quizNames.map(
async (quizName) =>
await fileStorageService.quizzes.getQuiz(courseName, moduleName, quizName)
)
),
await Promise.all(
pageNames.map(
async (pageName) =>
await fileStorageService.pages.getPage(courseName, moduleName, pageName)
)
),
]);
return {
moduleName,
assignmentNames,
pageNames,
quizNames,
assignments,
quizzes,
pages,
};
})
); );
await queryClient.prefetchQuery({ await queryClient.prefetchQuery({
@@ -84,68 +48,146 @@ export const hydrateCourse = async (
}); });
await Promise.all( await Promise.all(
modulesData.map( modulesData.map((d) => hydrateModuleData(d, courseName, queryClient))
async ({ );
moduleName, };
assignmentNames,
pageNames, export const hydrateCanvasCourse = async (
quizNames, canvasCourseId: number,
assignments, queryClient: QueryClient
quizzes, ) => {
pages, await Promise.all([
}) => { queryClient.prefetchQuery({
await queryClient.prefetchQuery({ queryKey: canvasAssignmentKeys.assignments(canvasCourseId),
queryKey: localCourseKeys.assignmentNames(courseName, moduleName), queryFn: async () => await canvasAssignmentService.getAll(canvasCourseId),
queryFn: () => assignmentNames, }),
}); queryClient.prefetchQuery({
await Promise.all( queryKey: canvasQuizKeys.quizzes(canvasCourseId),
assignments.map( queryFn: async () => await canvasQuizService.getAll(canvasCourseId),
async (assignment) => }),
await queryClient.prefetchQuery({ queryClient.prefetchQuery({
queryKey: localCourseKeys.assignment( queryKey: canvasPageKeys.pagesInCourse(canvasCourseId),
courseName, queryFn: async () => await canvasPageService.getAll(canvasCourseId),
moduleName, }),
assignment.name ]);
), };
queryFn: () => assignment,
}) const loadAllModuleData = async (courseName: string, moduleName: string) => {
const [assignmentNames, pageNames, quizNames] = await Promise.all([
await fileStorageService.assignments.getAssignmentNames(
courseName,
moduleName
),
await fileStorageService.pages.getPageNames(courseName, moduleName),
await fileStorageService.quizzes.getQuizNames(courseName, moduleName),
]);
const [assignments, quizzes, pages] = await Promise.all([
await Promise.all(
assignmentNames.map(
async (assignmentName) =>
await fileStorageService.assignments.getAssignment(
courseName,
moduleName,
assignmentName
) )
); )
await queryClient.prefetchQuery({ ),
queryKey: localCourseKeys.quizNames(courseName, moduleName), await Promise.all(
queryFn: () => quizNames, quizNames.map(
}); async (quizName) =>
await Promise.all( await fileStorageService.quizzes.getQuiz(
quizzes.map( courseName,
async (quiz) => moduleName,
await queryClient.prefetchQuery({ quizName
queryKey: localCourseKeys.quiz(
courseName,
moduleName,
quiz.name
),
queryFn: () => quiz,
})
) )
); )
await queryClient.prefetchQuery({ ),
queryKey: localCourseKeys.pageNames(courseName, moduleName), await Promise.all(
queryFn: () => pageNames, pageNames.map(
}); async (pageName) =>
await Promise.all( await fileStorageService.pages.getPage(
pages.map( courseName,
async (page) => moduleName,
await queryClient.prefetchQuery({ pageName
queryKey: localCourseKeys.page(
courseName,
moduleName,
page.name
),
queryFn: () => page,
})
) )
); )
} ),
]);
return {
moduleName,
assignmentNames,
pageNames,
quizNames,
assignments,
quizzes,
pages,
};
};
const hydrateModuleData = async (
{
moduleName,
assignmentNames,
pageNames,
quizNames,
assignments,
quizzes,
pages,
}: {
moduleName: string;
assignmentNames: string[];
pageNames: string[];
quizNames: string[];
assignments: LocalAssignment[];
quizzes: LocalQuiz[];
pages: LocalCoursePage[];
},
courseName: string,
queryClient: QueryClient
) => {
await queryClient.prefetchQuery({
queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
queryFn: () => assignmentNames,
});
await Promise.all(
assignments.map(
async (assignment) =>
await queryClient.prefetchQuery({
queryKey: localCourseKeys.assignment(
courseName,
moduleName,
assignment.name
),
queryFn: () => assignment,
})
)
);
await queryClient.prefetchQuery({
queryKey: localCourseKeys.quizNames(courseName, moduleName),
queryFn: () => quizNames,
});
await Promise.all(
quizzes.map(
async (quiz) =>
await queryClient.prefetchQuery({
queryKey: localCourseKeys.quiz(courseName, moduleName, quiz.name),
queryFn: () => quiz,
})
)
);
await queryClient.prefetchQuery({
queryKey: localCourseKeys.pageNames(courseName, moduleName),
queryFn: () => pageNames,
});
await Promise.all(
pages.map(
async (page) =>
await queryClient.prefetchQuery({
queryKey: localCourseKeys.page(courseName, moduleName, page.name),
queryFn: () => page,
})
) )
); );
}; };

View File

@@ -37,7 +37,6 @@ const _getDateFromISO = (value: string): Date | undefined => {
}; };
export const getDateFromString = (value: string): Date | undefined => { export const getDateFromString = (value: string): Date | undefined => {
const ampmDateRegex = const ampmDateRegex =
/^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}\s{1}[APap][Mm]$/; //"M/D/YYYY h:mm:ss AM/PM" /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}\s{1}[APap][Mm]$/; //"M/D/YYYY h:mm:ss AM/PM"
const militaryDateRegex = /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}$/; //"MM/DD/YYYY HH:mm:ss" const militaryDateRegex = /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}$/; //"MM/DD/YYYY HH:mm:ss"
@@ -52,7 +51,7 @@ export const getDateFromString = (value: string): Date | undefined => {
const [datePart, timePart] = value.split(" "); const [datePart, timePart] = value.split(" ");
return _getDateFromMilitary(datePart, timePart); return _getDateFromMilitary(datePart, timePart);
} else { } else {
console.log("invalid date format", value); if (value) console.log("invalid date format", value);
return undefined; return undefined;
} }
}; };
@@ -93,7 +92,6 @@ export const dateToMarkdownString = (date: Date) => {
return `${stringMonth}/${stringDay}/${stringYear} ${stringHours}:${stringMinutes}:${stringSeconds}`; return `${stringMonth}/${stringDay}/${stringYear} ${stringHours}:${stringMinutes}:${stringSeconds}`;
}; };
export const getDateOnlyMarkdownString = (date: Date) => { export const getDateOnlyMarkdownString = (date: Date) => {
return dateToMarkdownString(date).split(" ")[0] return dateToMarkdownString(date).split(" ")[0];
} };

View File

@@ -1,4 +1,4 @@
import { canvasApi, canvasServiceUtils } from "./canvasServiceUtils"; import { canvasApi, paginatedRequest } from "./canvasServiceUtils";
import { axiosClient } from "../axiosUtils"; import { axiosClient } from "../axiosUtils";
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup"; import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup"; import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
@@ -8,7 +8,7 @@ export const canvasAssignmentGroupService = {
async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> { async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> {
console.log("Requesting assignment groups"); console.log("Requesting assignment groups");
const url = `${canvasApi}/courses/${courseId}/assignment_groups`; const url = `${canvasApi}/courses/${courseId}/assignment_groups`;
const assignmentGroups = await canvasServiceUtils.paginatedRequest< const assignmentGroups = await paginatedRequest<
CanvasAssignmentGroup[] CanvasAssignmentGroup[]
>({ >({
url, url,

View File

@@ -1,75 +1,12 @@
import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment"; import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment";
import { import { canvasApi, paginatedRequest } from "./canvasServiceUtils";
canvasApi,
canvasServiceUtils,
paginatedRequest,
} from "./canvasServiceUtils";
import { LocalAssignment } from "@/models/local/assignment/localAssignment"; import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { axiosClient } from "../axiosUtils"; import { axiosClient } from "../axiosUtils";
import { markdownToHTMLSafe } from "../htmlMarkdownUtils"; import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse"; import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse";
import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils"; import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils";
import { import { getDateFromString } from "@/models/local/timeUtils";
getDateFromString,
getDateFromStringOrThrow,
} from "@/models/local/timeUtils";
const createRubric = async (
courseId: number,
assignmentCanvasId: number,
localAssignment: LocalAssignment
) => {
const criterion = localAssignment.rubric
.map((rubricItem) => ({
description: rubricItem.label,
points: rubricItem.points,
ratings: {
0: { description: "Full Marks", points: rubricItem.points },
1: { description: "No Marks", points: 0 },
},
}))
.reduce((acc, item, index) => {
return {
...acc,
[index]: item,
};
}, {} as { [key: number]: { description: string; points: number; ratings: { [key: number]: { description: string; points: number } } } });
const rubricBody = {
rubric_association_id: assignmentCanvasId,
rubric: {
title: `Rubric for Assignment: ${localAssignment.name}`,
association_id: assignmentCanvasId,
association_type: "Assignment",
use_for_grading: true,
criteria: criterion,
},
rubric_association: {
association_id: assignmentCanvasId,
association_type: "Assignment",
purpose: "grading",
use_for_grading: true,
},
};
const rubricUrl = `${canvasApi}/courses/${courseId}/rubrics`;
const rubricResponse = await axiosClient.post<CanvasRubricCreationResponse>(
rubricUrl,
rubricBody
);
if (!rubricResponse.data) throw new Error("Failed to create rubric");
const assignmentPointAdjustmentUrl = `${canvasApi}/courses/${courseId}/assignments/${assignmentCanvasId}`;
const assignmentPointAdjustmentBody = {
assignment: { points_possible: assignmentPoints(localAssignment) },
};
await axiosClient.put(
assignmentPointAdjustmentUrl,
assignmentPointAdjustmentBody
);
};
export const canvasAssignmentService = { export const canvasAssignmentService = {
async getAll(courseId: number): Promise<CanvasAssignment[]> { async getAll(courseId: number): Promise<CanvasAssignment[]> {
@@ -163,3 +100,60 @@ export const canvasAssignmentService = {
} }
}, },
}; };
const createRubric = async (
courseId: number,
assignmentCanvasId: number,
localAssignment: LocalAssignment
) => {
const criterion = localAssignment.rubric
.map((rubricItem) => ({
description: rubricItem.label,
points: rubricItem.points,
ratings: {
0: { description: "Full Marks", points: rubricItem.points },
1: { description: "No Marks", points: 0 },
},
}))
.reduce((acc, item, index) => {
return {
...acc,
[index]: item,
};
}, {} as { [key: number]: { description: string; points: number; ratings: { [key: number]: { description: string; points: number } } } });
const rubricBody = {
rubric_association_id: assignmentCanvasId,
rubric: {
title: `Rubric for Assignment: ${localAssignment.name}`,
association_id: assignmentCanvasId,
association_type: "Assignment",
use_for_grading: true,
criteria: criterion,
},
rubric_association: {
association_id: assignmentCanvasId,
association_type: "Assignment",
purpose: "grading",
use_for_grading: true,
},
};
const rubricUrl = `${canvasApi}/courses/${courseId}/rubrics`;
const rubricResponse = await axiosClient.post<CanvasRubricCreationResponse>(
rubricUrl,
rubricBody
);
if (!rubricResponse.data) throw new Error("Failed to create rubric");
const assignmentPointAdjustmentUrl = `${canvasApi}/courses/${courseId}/assignments/${assignmentCanvasId}`;
const assignmentPointAdjustmentBody = {
assignment: { points_possible: assignmentPoints(localAssignment) },
};
await axiosClient.put(
assignmentPointAdjustmentUrl,
assignmentPointAdjustmentBody
);
};

View File

@@ -14,18 +14,26 @@ const getNextUrl = (
? (headers.get("link") as string) ? (headers.get("link") as string)
: ((headers as RawAxiosResponseHeaders)["link"] as string); : ((headers as RawAxiosResponseHeaders)["link"] as string);
if (!linkHeader) return undefined; if (!linkHeader) {
console.log("could not find link header in the response");
return undefined;
}
const links = linkHeader.split(",").map((link) => link.trim()); const links = linkHeader.split(",").map((link) => link.trim());
const nextLink = links.find((link) => link.includes('rel="next"')); const nextLink = links.find((link) => link.includes('rel="next"'));
if (!nextLink) return undefined; if (!nextLink) {
// console.log(
// "could not find next url in link header, reached end of pagination"
// );
return undefined;
}
const nextUrl = nextLink.split(";")[0].trim().slice(1, -1); const nextUrl = nextLink.split(";")[0].trim().slice(1, -1);
return nextUrl; return nextUrl;
}; };
export async function paginatedRequest<T>(request: { export async function paginatedRequest<T extends any[]>(request: {
url: string; url: string;
}): Promise<T> { }): Promise<T> {
var requestCount = 1; var requestCount = 1;
@@ -36,20 +44,19 @@ export async function paginatedRequest<T>(request: {
url.toString() url.toString()
); );
if (!Array.isArray(firstData)) { // if (!Array.isArray(firstData)) {
return firstData; // return firstData;
} // }
var returnData = [...firstData];
var returnData = firstData ? [firstData] : [];
var nextUrl = getNextUrl(firstHeaders); var nextUrl = getNextUrl(firstHeaders);
console.log("got first request", nextUrl, firstHeaders); // console.log("got first request", nextUrl, firstHeaders);
while (nextUrl) { while (nextUrl) {
requestCount += 1; requestCount += 1;
const { data, headers } = await axiosClient.get<T>(nextUrl); const { data, headers } = await axiosClient.get<T>(nextUrl);
if (data) { if (data) {
returnData = [...returnData, data]; returnData = returnData.concat(data);
} }
nextUrl = getNextUrl(headers); nextUrl = getNextUrl(headers);
} }
@@ -60,5 +67,5 @@ export async function paginatedRequest<T>(request: {
); );
} }
return returnData; return returnData as T;
} }