-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapp.js
More file actions
159 lines (131 loc) · 5.38 KB
/
app.js
File metadata and controls
159 lines (131 loc) · 5.38 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
159
//docs: https://octokit.github.io/node-github
const fs = require('fs');
const csvParse = require('csv-parse/lib/sync');
const request = require('request-promise');
const _ = require('lodash');
const { Octokit } = require("octokit");
const {
restEndpointMethods,
} = require("@octokit/plugin-rest-endpoint-methods");
const MyOctokit = Octokit.plugin(restEndpointMethods);
const octokit = new MyOctokit({
auth: fs.readFileSync('./github.token').toString()
});
// const octokit = require('@octokit/rest')();
// octokit.authenticate({
// type: 'oauth',
// token: fs.readFileSync('./github.token').toString()
// })
const CANVAS_API_BASE = 'https://canvas.uw.edu/api/v1';
const CANVAS_KEY = fs.readFileSync('./canvas.key').toString();
const USER_AGENT_STRING = 'CLI'; //adding user agent string per
/** Read Assignment Info **/
//Overall access stubs
const COURSE = require('./course.json');
//get single assignment from command line args (as -p argument; specify repo slug)
//get score from assignment (as -s, percentage number)
const args = require('minimist')(process.argv.slice(2));
if(args.p) {
COURSE.assignments = COURSE.assignments.filter(a => a.repo_slug == args.p)
}
const SCORE = args.s || 100
/** Scoring Script **/
//score ALL of the submissions in the course list
async function scoreAllAssignments() {
const students = await getStudents('./students.csv');
for(assignment of COURSE.assignments){
await scoreSubmission(students, assignment).catch(console.error);
}
}
scoreAllAssignments(); //run it
//score a single assignment for given students
async function scoreSubmission(students, assignment){
let totalComplete = 0;
for(student of students) {
console.log(`Scoring ${assignment.repo_slug} for ${student.display_name}`);
if(student.github) student.github = student.github.trim(); //trim if exists
const checks = await octokit.rest.checks.listForRef({
owner: COURSE.github_org,
repo: assignment.repo_slug+'-'+student.github,
ref: assignment.branch || 'main', //default to main
}).catch((err) => {
console.log("Error accessing Githhub:", err.response.data.message);
})
try {
const sorted = checks.data.check_runs
.filter(check => check.name == "test") //only get Jest Test TODO: UPDATE ME!
.sort((a, b) => { //get most recent
return new Date(b.completed_at) == new Date(a.completed_at)
})
if(sorted[0].conclusion === 'success'){
console.log(`...complete!`)
await markComplete(student, assignment);
totalComplete++;
} else {
console.log(`...INCOMPLETE`);
}
} catch(err) {
console.log(`...INCOMPLETE`);
}
}
console.log(`Complete submissions: ${totalComplete}/${students.length}`)
}
//Mark a particular student's assignment as complete (100%) in Canvas
async function markComplete(student, assignment) {
let url = CANVAS_API_BASE + `/courses/${COURSE.canvas_id}/assignments/${assignment.canvas_id}/submissions/${student.canvas_id}`+`?access_token=${CANVAS_KEY}`;
let req = { method:'PUT', uri: url, form: {submission: {posted_grade:SCORE+'%'}},
headers: { 'User-Agent': USER_AGENT_STRING}, //specify user agent
}; //request
return request(req)
.catch((err) => {
console.error("Error marking submission: ", err.message);
});
}
//read and compile student data (from Canvas and `students.csv`)
async function getStudents(studentFile) {
let studentGitHubs = csvParse(fs.readFileSync(studentFile), {'columns':true, 'bom':true});
let canvasIds = await getEnrollments();
let students = canvasIds.map((student) => {
//console.log(student.uwnetid);
let match = _.find(studentGitHubs, {'uwnetid': student.uwnetid});
return _.merge(student,match);
})
students = _.sortBy(students, ['display_name']); //ordering
let githubLess = students.filter((s)=> s.github === undefined);
if(githubLess.length > 0){
console.log(`${githubLess.length} enrolled students without a GitHub account:`)
githubLess.forEach((s) => console.log(s.uwnetid));
}
return students;
}
//fetch all student canvasIds from Canvas API
async function getEnrollments(){
let PER_PAGE = 100;
let url = CANVAS_API_BASE + `/courses/${COURSE.canvas_id}/enrollments?per_page=${PER_PAGE}&type=StudentEnrollment&access_token=${CANVAS_KEY}`
let req = { method:'GET', uri: url, resolveWithFullResponse: true,
headers: { 'User-Agent': USER_AGENT_STRING}, //specify user agent
}; //request
let enrollmentsRes = await request(req); //has full data
let enrollments = JSON.parse(enrollmentsRes.body); //get initial enrollments
let studentIds = enrollments.map((item) => {
return {
canvas_id: item.user.id,
uwnetid: item.user.login_id,
display_name: item.user.sortable_name
}
});
let paginationLinks = enrollmentsRes.caseless.dict.link;
let nextLink = paginationLinks.split(",").filter((link) => link.includes("next"))[0]
if(nextLink) { //if paginated
let nextLinkUrl = nextLink.slice(1, nextLink.indexOf('>'))
enrollments = await request({method:'GET', uri:`${nextLinkUrl}&access_token=${CANVAS_KEY}`, headers: { 'User-Agent': USER_AGENT_STRING},}).then(JSON.parse) //get the page
studentIds = studentIds.concat(enrollments.map((item) => {
return {
canvas_id: item.user.id,
uwnetid: item.user.login_id,
display_name: item.user.sortable_name
}
}))
}
return studentIds;
}