Skip to content

Commit 2a0508f

Browse files
author
Andrew Scholer
committed
Assibment builder - add explicit chapter/subchapter numbers to builder pages
1 parent 1eb4308 commit 2a0508f

7 files changed

Lines changed: 101 additions & 26 deletions

File tree

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/reading/AssignmentReadingsTable.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
white-space: nowrap;
33
}
44

5+
.chapterCell {
6+
min-width: 0;
7+
max-width: 16rem;
8+
}
9+
510
.sectionCell {
611
min-width: 0;
712
max-width: 24rem;

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/reading/AssignmentReadingsTable.spec.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ const READINGS: Exercise[] = [
3131
id: 1,
3232
name: "Welcome",
3333
title: "Welcome",
34-
chapter: "Intro",
34+
chapter: "intro",
35+
chapter_name: "Intro",
36+
chapter_num: 1,
3537
subchapter: "intro-welcome",
38+
sub_chapter_name: "Welcome",
39+
sub_chapter_num: 1,
3640
numQuestions: 10,
3741
activities_required: 6,
3842
points: 5,
@@ -42,8 +46,12 @@ const READINGS: Exercise[] = [
4246
id: 2,
4347
name: "Variables",
4448
title: "Variables",
45-
chapter: "Basics",
49+
chapter: "basics",
50+
chapter_name: "Basics",
51+
chapter_num: 2,
4652
subchapter: "basics-variables",
53+
sub_chapter_name: "Variables",
54+
sub_chapter_num: 3,
4755
numQuestions: 0,
4856
activities_required: 0,
4957
points: 3,
@@ -66,16 +74,21 @@ const baseProps = {
6674
describe("AssignmentReadingsTable", () => {
6775
it("renders one row per reading with chapter and section", () => {
6876
renderWithMantine(<AssignmentReadingsTable {...baseProps} />);
69-
expect(screen.getByText("Intro")).toBeInTheDocument();
70-
expect(screen.getByText("Welcome")).toBeInTheDocument();
71-
expect(screen.getByText("Basics")).toBeInTheDocument();
72-
expect(screen.getByText("Variables")).toBeInTheDocument();
77+
expect(screen.getByText("1 Intro")).toBeInTheDocument();
78+
expect(screen.getByText("intro")).toBeInTheDocument();
79+
expect(screen.getByText("1.1 Welcome")).toBeInTheDocument();
80+
expect(screen.getByText("intro-welcome")).toBeInTheDocument();
81+
expect(screen.getByText("2 Basics")).toBeInTheDocument();
82+
expect(screen.getByText("basics")).toBeInTheDocument();
83+
expect(screen.getByText("2.3 Variables")).toBeInTheDocument();
84+
expect(screen.getByText("basics-variables")).toBeInTheDocument();
7385
});
7486

7587
it("filters readings by chapter or section text", () => {
7688
renderWithMantine(<AssignmentReadingsTable {...baseProps} globalFilter="basics" />);
77-
expect(screen.queryByText("Welcome")).not.toBeInTheDocument();
78-
expect(screen.getByText("Variables")).toBeInTheDocument();
89+
expect(screen.queryByText("1.1 Welcome")).not.toBeInTheDocument();
90+
expect(screen.getByText("2.3 Variables")).toBeInTheDocument();
91+
expect(screen.getByText("basics-variables")).toBeInTheDocument();
7992
});
8093

8194
it("defaults the activity count to at least one and required to 80 percent", () => {

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/reading/AssignmentReadingsTable.tsx

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,28 @@ interface AssignmentReadingsTableProps {
3535
const getNumQuestionsOrDefault = (numQuestions: Nullable<number>): number =>
3636
Math.max(numQuestions ?? 0, 1);
3737

38+
const formatNumberedLabel = (label: string, numbers: Array<number | null | undefined>): string => {
39+
const prefix = numbers
40+
.filter((value): value is number => value !== null && value !== undefined)
41+
.join(".");
42+
43+
return prefix ? [prefix, label].filter(Boolean).join(" ") : label;
44+
};
45+
3846
const matchesFilter = (reading: Exercise, filter: string): boolean => {
3947
const query = filter.trim().toLowerCase();
4048

4149
if (!query) {
4250
return true;
4351
}
44-
return [reading.name, reading.title, reading.chapter, reading.subchapter]
52+
return [
53+
reading.name,
54+
reading.title,
55+
reading.chapter_name,
56+
reading.sub_chapter_name,
57+
reading.chapter,
58+
reading.subchapter
59+
]
4560
.filter(Boolean)
4661
.some((field) => field!.toLowerCase().includes(query));
4762
};
@@ -75,17 +90,32 @@ export const AssignmentReadingsTable = ({
7590
{
7691
key: "chapter",
7792
header: "Chapter",
78-
width: "12rem",
79-
render: (row) => <div className={styles.nowrap}>{row.chapter}</div>
93+
width: "16rem",
94+
render: (row) => (
95+
<div className={styles.chapterCell}>
96+
<div className={styles.sectionTitle} title={row.chapter_name || row.chapter}>
97+
{formatNumberedLabel(row.chapter_name || row.chapter, [row.chapter_num])}
98+
</div>
99+
<div className={styles.sectionPath} title={row.chapter || undefined}>
100+
{row.chapter}
101+
</div>
102+
</div>
103+
)
80104
},
81105
{
82106
key: "subchapter",
83107
header: "Section",
84108
width: "20rem",
85109
render: (row) => (
86110
<div className={styles.sectionCell}>
87-
<div className={styles.sectionTitle} title={row.name || row.title}>
88-
{row.name || row.title}
111+
<div
112+
className={styles.sectionTitle}
113+
title={row.sub_chapter_name || row.title || row.name || row.subchapter}
114+
>
115+
{formatNumberedLabel(
116+
row.sub_chapter_name || row.title || row.name || row.subchapter,
117+
[row.chapter_num, row.sub_chapter_num]
118+
)}
89119
</div>
90120
<div className={styles.sectionPath} title={row.subchapter || undefined}>
91121
{row.subchapter}

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/reading/components/ChooseReadingsButton.spec.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ const { mockUpdate, mockReadingsSelector, updatingHolder, READINGS_TREE } = vi.h
1212
READINGS_TREE: [
1313
{
1414
key: "chapter-1",
15-
data: { title: "Chapter 1" },
15+
data: { title: "Chapter 1", chapter_num: 1 },
1616
children: [
17-
{ key: "sec-1", data: { title: "Section 1", id: 11 } },
18-
{ key: "sec-2", data: { title: "Section 2", id: 12 } }
17+
{ key: "sec-1", data: { title: "Section 1", id: 11, chapter_num: 1, sub_chapter_num: 1 } },
18+
{ key: "sec-2", data: { title: "Section 2", id: 12, chapter_num: 1, sub_chapter_num: 2 } }
1919
]
2020
}
2121
] as TreeNode[]
@@ -46,18 +46,18 @@ describe("ChooseReadingsButton", () => {
4646

4747
it("reveals the readings tree when the button is clicked", async () => {
4848
renderWithMantine(<ChooseReadingsButton />);
49-
expect(screen.queryByText("Chapter 1")).not.toBeInTheDocument();
49+
expect(screen.queryByText("1 Chapter 1")).not.toBeInTheDocument();
5050

5151
await userEvent.click(screen.getByRole("button", { name: "Choose readings" }));
52-
expect(screen.getByText("Chapter 1")).toBeInTheDocument();
52+
expect(screen.getByText("1 Chapter 1")).toBeInTheDocument();
5353
});
5454

5555
it("adds the selected leaf readings on select", async () => {
5656
renderWithMantine(<ChooseReadingsButton />);
5757
await userEvent.click(screen.getByRole("button", { name: "Choose readings" }));
58-
await userEvent.click(screen.getByRole("button", { name: "Expand Chapter 1" }));
58+
await userEvent.click(screen.getByRole("button", { name: "Expand 1 Chapter 1" }));
5959

60-
await userEvent.click(screen.getByRole("checkbox", { name: "Select Section 1" }));
60+
await userEvent.click(screen.getByRole("checkbox", { name: "Select 1.1 Section 1" }));
6161

6262
expect(mockUpdate).toHaveBeenCalledWith({ idsToAdd: [11], isReading: true });
6363
});
@@ -70,9 +70,9 @@ describe("ChooseReadingsButton", () => {
7070

7171
renderWithMantine(<ChooseReadingsButton />);
7272
await userEvent.click(screen.getByRole("button", { name: "Choose readings" }));
73-
await userEvent.click(screen.getByRole("button", { name: "Expand Chapter 1" }));
73+
await userEvent.click(screen.getByRole("button", { name: "Expand 1 Chapter 1" }));
7474

75-
await userEvent.click(screen.getByRole("checkbox", { name: "Select Section 1" }));
75+
await userEvent.click(screen.getByRole("checkbox", { name: "Select 1.1 Section 1" }));
7676

7777
expect(mockUpdate).toHaveBeenCalledWith({ idsToRemove: [99], isReading: true });
7878
});
@@ -100,9 +100,9 @@ describe("ChooseReadingsButton", () => {
100100

101101
renderWithMantine(<ChooseReadingsButton />);
102102
await userEvent.click(screen.getByRole("button", { name: "Choose readings" }));
103-
await userEvent.click(screen.getByRole("button", { name: "Expand Chapter 1" }));
103+
await userEvent.click(screen.getByRole("button", { name: "Expand 1 Chapter 1" }));
104104

105-
await userEvent.click(screen.getByRole("checkbox", { name: "Select Section 1" }));
105+
await userEvent.click(screen.getByRole("checkbox", { name: "Select 1.1 Section 1" }));
106106

107107
expect(mockUpdate).not.toHaveBeenCalled();
108108
});

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/reading/components/ChooseReadingsButton.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,29 @@ import { getLeafNodes } from "@/utils/exercise";
1313

1414
import styles from "./ChooseReadingsButton.module.css";
1515

16+
type NumberedNodeData = Partial<Exercise> & {
17+
chapter_num?: number;
18+
sub_chapter_num?: number;
19+
};
20+
21+
const formatNumberedNodeLabel = (node: TreeNode): string => {
22+
const data = node.data as NumberedNodeData | undefined;
23+
const title = data?.title ?? String(node.key);
24+
const numbers =
25+
data?.sub_chapter_num !== null && data?.sub_chapter_num !== undefined
26+
? [data.chapter_num, data.sub_chapter_num]
27+
: [data?.chapter_num ?? data?.num];
28+
const prefix = numbers
29+
.filter((value): value is number => value !== null && value !== undefined)
30+
.join(".");
31+
32+
return prefix ? [prefix, title].filter(Boolean).join(" ") : title;
33+
};
34+
1635
const READINGS_COLUMNS: TreeTableColumn[] = [
1736
{
1837
header: "Select readings",
19-
render: (node) => <span>{(node.data as Exercise)?.title}</span>
38+
render: (node) => <span>{formatNumberedNodeLabel(node)}</span>
2039
}
2140
];
2241

@@ -83,7 +102,7 @@ export const ChooseReadingsButton = () => {
83102
onSelect={onSelect}
84103
onUnselect={onUnselect}
85104
ariaLabel="Choose readings"
86-
getNodeLabel={(node) => (node.data as Exercise)?.title ?? String(node.key)}
105+
getNodeLabel={formatNumberedNodeLabel}
87106
/>
88107
</div>
89108
<div className={styles.footer}>

bases/rsptx/assignment_server_api/assignment_builder/src/types/exercises.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export type Exercise = {
6868
owner: string;
6969
tags: string;
7070
num: number;
71+
chapter_num?: number;
72+
sub_chapter_num?: number;
73+
chapter_name?: string;
74+
sub_chapter_name?: string;
7175
numQuestions: number;
7276
required: boolean;
7377
title: string;

bases/rsptx/assignment_server_api/routers/instructor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,10 @@ async def get_assignment_questions(
966966
# augment the assignment question with additional question data
967967
aq["name"] = q["name"]
968968
aq["subchapter"] = q["subchapter"]
969+
aq["chapter_num"] = row.Chapter.chapter_num
970+
aq["sub_chapter_num"] = row.SubChapter.sub_chapter_num
971+
aq["chapter_name"] = row.Chapter.chapter_name
972+
aq["sub_chapter_name"] = row.SubChapter.sub_chapter_name
969973
aq["chapter"] = q["chapter"]
970974
aq["base_course"] = q["base_course"]
971975
aq["htmlsrc"] = q["htmlsrc"]

0 commit comments

Comments
 (0)