Skip to content

Commit 4c120af

Browse files
jasminesovphrdang
andauthored
Move graphs from Timeline to Summary tab (#112)
* wip * hardcoded graphs on summary tab * Run prettier * moved graphs under histograms * ran prettier * Delete unused code, add progress bar * Run prettier * Run prettier --------- Co-authored-by: Rebecca Dang <rdang@berkeley.edu>
1 parent 96bd029 commit 4c120af

4 files changed

Lines changed: 213 additions & 156 deletions

File tree

src/snapshots-app/client/bundles/components/submission/tabs/summary/SummaryTab.jsx

Lines changed: 142 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect, useMemo } from "react";
2+
import { LineChart } from "@mui/x-charts/LineChart";
23
import {
34
Box,
45
Container,
@@ -78,25 +79,61 @@ function SummaryTab({}) {
7879
const [activeIndex, setActiveIndex] = useState(0);
7980
const routeParams = useParams();
8081
const [summaryStats, setSummaryStats] = useState(null);
82+
const [backupData, setBackupData] = useState(null);
83+
const [fileMetadata, setFileMetadata] = useState(null);
8184

8285
useEffect(() => {
8386
fetch(
8487
`/api/summary_statistics/${routeParams.courseId}/${routeParams.assignmentId}/${routeParams.studentId}`,
85-
{
86-
method: "GET",
87-
},
88+
{ method: "GET" },
8889
)
8990
.then((response) => {
90-
if (!response.ok) {
91+
if (!response.ok)
9192
throw new Error(`HTTP error! Status: ${response.status}`);
92-
}
9393
return response.json();
9494
})
95-
.then((responseData) => {
96-
setSummaryStats(responseData);
97-
});
95+
.then((data) => setSummaryStats(data));
9896
}, [routeParams]);
9997

98+
useEffect(() => {
99+
fetch(
100+
`/api/backups/${routeParams.courseId}/${routeParams.assignmentId}/${routeParams.studentId}`,
101+
)
102+
.then((res) => res.json())
103+
.then((data) => setBackupData(data));
104+
}, [routeParams]);
105+
106+
useEffect(() => {
107+
fetch(
108+
`/api/backup_file_metadata/${routeParams.courseId}/${routeParams.assignmentId}/${routeParams.studentId}`,
109+
)
110+
.then((res) => res.json())
111+
.then((data) => setFileMetadata(data));
112+
}, [routeParams]);
113+
114+
const backupTimestamps =
115+
backupData?.backups.map((b) => new Date(b.created)) ?? [];
116+
const xAxis = [{ data: backupTimestamps, scaleType: "time", label: "Date" }];
117+
const height = 300;
118+
119+
const firstFile = fileMetadata
120+
? Object.keys(fileMetadata.files_to_metadata)[0]
121+
: null;
122+
const numLines = firstFile
123+
? fileMetadata.files_to_metadata[firstFile].num_lines
124+
: [];
125+
126+
const numQuestionsSolved =
127+
backupData?.backups.map((b) => b.history.filter((h) => h.solved).length) ??
128+
[];
129+
const numQuestionsUnsolved =
130+
backupData?.backups.map((b) => b.history.filter((h) => !h.solved).length) ??
131+
[];
132+
const numAttempts =
133+
backupData?.backups.map((b) =>
134+
b.history.reduce((sum, h) => sum + (h.attempts ?? 0), 0),
135+
) ?? [];
136+
100137
const menuItems = useMemo(
101138
() =>
102139
summaryStats
@@ -195,6 +232,8 @@ function SummaryTab({}) {
195232
[summaryStats],
196233
);
197234

235+
const chartsReady = backupData && fileMetadata && backupTimestamps.length > 0;
236+
198237
return (
199238
<Container maxWidth="xl" sx={{ py: 4 }}>
200239
<div
@@ -205,72 +244,108 @@ function SummaryTab({}) {
205244
marginBottom: "2rem",
206245
}}
207246
>
208-
<Typography variant="h4">Summary Statistics</Typography>
209-
<InfoTooltip info="Summary statistics about this student's performance on this assignment, with comparisons to other students" />
247+
<Typography variant="h4">Summary</Typography>
248+
<InfoTooltip info="Summary statistics and visualizations about this student's performance on this assignment" />
210249
</div>
211250

212251
{menuItems.length > 0 ? (
213-
<>
214-
{/* TODO: generalize this left sidebar + main area into a component */}
215-
<Box sx={{ display: "flex", gap: 3, minHeight: "80vh" }}>
216-
{/* Left Sidebar */}
217-
<Paper
218-
elevation={2}
219-
sx={{
220-
width: 240,
221-
flexShrink: 0,
222-
borderRadius: 2,
223-
overflow: "hidden",
224-
}}
225-
>
226-
<List>
227-
{menuItems.map((item, index) => (
228-
<ListItem key={item.text} disablePadding>
229-
<ListItemButton
230-
selected={activeIndex === index}
231-
onClick={() => setActiveIndex(index)}
232-
>
233-
<ListItemIcon>{item.icon}</ListItemIcon>
234-
<ListItemText primary={item.text} />
252+
<Box sx={{ display: "flex", gap: 3, minHeight: "80vh" }}>
253+
{/* Left Sidebar */}
254+
<Paper
255+
elevation={2}
256+
sx={{
257+
width: 240,
258+
flexShrink: 0,
259+
borderRadius: 2,
260+
overflow: "hidden",
261+
}}
262+
>
263+
<List>
264+
{menuItems.map((item, index) => (
265+
<ListItem key={item.text} disablePadding>
266+
<ListItemButton
267+
selected={activeIndex === index}
268+
onClick={() => setActiveIndex(index)}
269+
>
270+
<ListItemIcon>{item.icon}</ListItemIcon>
271+
<ListItemText primary={item.text} />
235272

236-
<Box
237-
onClick={(e) => e.stopPropagation()}
238-
sx={{
239-
ml: "auto",
240-
display: "flex",
241-
alignItems: "center",
242-
}}
243-
>
244-
{item.tooltip ? (
245-
<InfoTooltip info={item.tooltip} />
246-
) : null}
247-
</Box>
248-
</ListItemButton>
249-
</ListItem>
250-
))}
251-
</List>
252-
</Paper>
253-
254-
{/* Main Content Area */}
255-
<Paper
256-
elevation={1}
257-
sx={{
258-
flexGrow: 1,
259-
p: 4,
260-
borderRadius: 2,
261-
backgroundColor: "#fafafa",
262-
}}
263-
>
264-
{menuItems[activeIndex].component}
265-
</Paper>
266-
</Box>
273+
<Box
274+
onClick={(e) => e.stopPropagation()}
275+
sx={{ ml: "auto", display: "flex", alignItems: "center" }}
276+
>
277+
{item.tooltip ? (
278+
<InfoTooltip info={item.tooltip} />
279+
) : null}
280+
</Box>
281+
</ListItemButton>
282+
</ListItem>
283+
))}
284+
</List>
285+
</Paper>
267286

268-
{/* <ProblemGanttPlot /> */}
287+
{/* Main Content Area */}
288+
<Paper
289+
elevation={1}
290+
sx={{
291+
flexGrow: 1,
292+
p: 4,
293+
borderRadius: 2,
294+
backgroundColor: "#fafafa",
295+
}}
296+
>
297+
{menuItems[activeIndex].component}
298+
</Paper>
299+
</Box>
300+
) : (
301+
<CircularProgress />
302+
)}
269303

270-
<BackupCalendarChart />
304+
<BackupCalendarChart />
305+
<BackupGanttPlot />
271306

272-
<BackupGanttPlot />
273-
</>
307+
{chartsReady ? (
308+
<div style={{ marginTop: "2rem" }}>
309+
<LineChart
310+
xAxis={xAxis}
311+
series={[
312+
{
313+
curve: "linear",
314+
data: numLines,
315+
label: `# of lines in ${firstFile}`,
316+
},
317+
]}
318+
height={height}
319+
/>
320+
<LineChart
321+
margin={{ top: 100 }}
322+
xAxis={xAxis}
323+
series={[
324+
{
325+
curve: "linear",
326+
data: numQuestionsSolved,
327+
label: "# of questions solved",
328+
},
329+
{
330+
curve: "linear",
331+
data: numQuestionsUnsolved,
332+
label: "# of questions unsolved",
333+
},
334+
]}
335+
height={height}
336+
/>
337+
<LineChart
338+
xAxis={xAxis}
339+
series={[
340+
{
341+
curve: "linear",
342+
data: numAttempts,
343+
label: "# of attempts",
344+
},
345+
]}
346+
height={height}
347+
/>
348+
</div>
274349
) : (
275350
<CircularProgress />
276351
)}

src/snapshots-app/client/bundles/components/submission/tabs/timeline/Graphs.jsx

Lines changed: 26 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
import React from "react";
22

3-
import { LineChart } from "@mui/x-charts/LineChart";
4-
import InfoTooltip from "../../../common/InfoTooltip";
3+
import { LinearProgress } from "@mui/material";
54
import DoneIcon from "@mui/icons-material/Done";
65
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
76
import { Tooltip } from "@mui/material";
7+
import Typography from "@mui/material/Typography";
8+
import Box from "@mui/material/Box";
9+
10+
function LinearProgressWithLabel(props) {
11+
return (
12+
<Box sx={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}>
13+
<Box sx={{ width: "100%", mr: 1 }}>
14+
<LinearProgress
15+
variant="determinate"
16+
aria-label="Upload photos"
17+
{...props}
18+
/>
19+
</Box>
20+
<Box sx={{ minWidth: 35 }}>
21+
<Typography
22+
variant="body2"
23+
sx={{ color: "text.secondary" }}
24+
>{`${Math.round(props.value)}%`}</Typography>
25+
</Box>
26+
</Box>
27+
);
28+
}
829

930
function AssignmentProblems({ history, allProblemDisplayNames, numSolved }) {
1031
function getIcon(problemDisplayName) {
@@ -38,101 +59,27 @@ function AssignmentProblems({ history, allProblemDisplayNames, numSolved }) {
3859

3960
return (
4061
<div style={{ paddingTop: "1rem", paddingBottom: "1rem" }}>
41-
<div style={{ fontWeight: "bold" }}>
42-
Assignment Progress ({Math.round(getPercentSolved())}% solved)
43-
</div>
62+
<LinearProgressWithLabel value={Math.round(getPercentSolved())} />
4463
<div>{problems}</div>
4564
</div>
4665
);
4766
}
4867

68+
// TODO rename for consistency with UI
4969
function Graphs({
50-
file,
51-
backupCreatedTimestamps,
52-
fileMetadata,
5370
numQuestionsSolved,
54-
numQuestionsUnsolved,
55-
numAttempts,
5671
currBackupHistory,
5772
allProblemDisplayNames,
5873
selectedBackup,
5974
}) {
60-
const dates = backupCreatedTimestamps.map(
61-
(dateString) => new Date(dateString),
62-
);
63-
const xAxis = [
64-
{
65-
data: dates,
66-
scaleType: "time",
67-
valueFormatter: formatDate,
68-
label: "Date",
69-
},
70-
];
71-
const height = 300;
72-
const GRAPHS_TOOLTIP_INFO =
73-
"Visualize the student's progress over time. Note that a question may be comprised of multiple tests and an attempt is defined as running an OkPy command.";
74-
75-
function formatDate(date) {
76-
return date.toLocaleString("en-US", {
77-
month: "2-digit",
78-
day: "2-digit",
79-
hour: "2-digit",
80-
minute: "2-digit",
81-
second: "2-digit",
82-
hour12: true,
83-
});
84-
}
85-
8675
return (
8776
<div>
88-
<div style={{ fontSize: "1.5rem" }}>
89-
Assignment Insights{" "}
90-
<InfoTooltip info={GRAPHS_TOOLTIP_INFO} placement="top" />
91-
</div>
77+
<div style={{ fontSize: "1.5rem" }}>Progress</div>
9278
<AssignmentProblems
9379
history={currBackupHistory}
9480
allProblemDisplayNames={allProblemDisplayNames}
9581
numSolved={numQuestionsSolved[selectedBackup]}
9682
/>
97-
<LineChart
98-
xAxis={xAxis}
99-
series={[
100-
{
101-
curve: "linear",
102-
data: fileMetadata.num_lines,
103-
label: `# of lines in ${file}`,
104-
},
105-
]}
106-
height={height}
107-
/>
108-
<LineChart
109-
margin={{ top: 100 }}
110-
xAxis={xAxis}
111-
series={[
112-
{
113-
curve: "linear",
114-
data: numQuestionsSolved,
115-
label: "# of questions solved",
116-
},
117-
{
118-
curve: "linear",
119-
data: numQuestionsUnsolved,
120-
label: "# of questions unsolved",
121-
},
122-
]}
123-
height={height}
124-
/>
125-
<LineChart
126-
xAxis={xAxis}
127-
series={[
128-
{
129-
curve: "linear",
130-
data: numAttempts,
131-
label: "# of attempts",
132-
},
133-
]}
134-
height={height}
135-
/>
13683
</div>
13784
);
13885
}

0 commit comments

Comments
 (0)