-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsakai-csv-export.html
More file actions
158 lines (145 loc) · 5.04 KB
/
sakai-csv-export.html
File metadata and controls
158 lines (145 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sakai Gradebook Export</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
textarea {
width: 100%;
height: 150px;
margin-bottom: 10px;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
margin-right: 10px;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
background-color: #f0f0f0;
padding: 10px;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>Sakai Gradebook Export</h1>
<p>Paste a list of Site IDs below (one per line) to export the gradebook:</p>
<textarea id="siteIds" placeholder="Enter Site IDs here..."></textarea>
<label>
<input type="checkbox" id="useUsername"> Use displayIds instead of User IDs (may be slower)
</label>
<button onclick="startExport()">Start</button>
<pre id="output"></pre>
<script>
const userCache = {};
async function fetchGradebookData(siteId) {
const url = `/direct/gradebook/batch.json?siteIds=${siteId}`;
console.log(`Fetching data from: ${url}`);
try {
const response = await fetch(url, { credentials: 'include' });
console.log(`Response status for ${siteId}: ${response.status}`);
const text = await response.text();
console.log(`Raw response for ${siteId}:`, text);
if (!response.ok) {
console.error(`Error fetching site ${siteId}: ${response.statusText}`);
return null;
}
const data = JSON.parse(text);
console.log(`Parsed JSON data for ${siteId}:`, data);
return data;
} catch (error) {
console.error(`Fetch failed for ${siteId}:`, error);
return null;
}
}
async function fetchUsername(userId) {
if (userCache[userId]) {
return userCache[userId];
}
const url = `/direct/user/${userId}.json`;
try {
const response = await fetch(url, { credentials: 'include' });
if (!response.ok) {
console.error(`Error fetching user ${userId}: ${response.statusText}`);
return userId;
}
const data = await response.json();
userCache[userId] = data.displayId;
return data.displayId;
} catch (error) {
console.error(`Fetch failed for user ${userId}:`, error);
return userId;
}
}
async function convertToCSV(data) {
if (!data || !data.gradebook_collection || !Array.isArray(data.gradebook_collection) || data.gradebook_collection.length === 0) {
console.warn("No valid data for CSV conversion.");
return '';
}
const siteData = data.gradebook_collection[0];
const categories = Object.fromEntries(siteData.categories.map(cat => [cat.id, cat.name]));
const headers = ["User ID", "Grade Item", "Category", "Grade", "Max Points", "Date Graded"];
const rows = [];
const useUsername = document.getElementById('useUsername').checked;
for (const item of siteData.gradeItems) {
for (const grade of item.grades) {
const userId = useUsername ? await fetchUsername(grade.userId) : grade.userId;
rows.push([
userId,
item.name,
categories[item.categoryId] || "Uncategorized",
grade.grade,
item.points,
new Date(grade.dateGraded).toISOString()
].join(','));
}
}
return [headers.join(','), ...rows].join('\n');
}
function downloadCSV(siteId, csvContent) {
if (!csvContent) {
console.warn(`Skipping empty CSV for ${siteId}`);
return;
}
const blob = new Blob([csvContent], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `${siteId}_gradebook.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
async function startExport() {
const siteIds = document.getElementById('siteIds').value.trim().split('\n').map(id => id.trim()).filter(id => id);
const output = document.getElementById('output');
output.textContent = 'Processing...';
console.log("Starting export for Site IDs:", siteIds);
for (const siteId of siteIds) {
output.textContent += `\nProcessing: ${siteId}`;
const jsonData = await fetchGradebookData(siteId);
if (jsonData) {
const csvData = await convertToCSV(jsonData);
if (csvData) {
downloadCSV(siteId, csvData);
output.textContent += `\nExported: ${siteId}`;
} else {
output.textContent += `\nNo valid data for: ${siteId}`;
console.warn(`No valid data for site ${siteId}`);
}
} else {
output.textContent += `\nFailed: ${siteId}`;
}
}
console.log("Export process completed.");
}
</script>
</body>
</html>