9
9
GRADING_REPO : cbfacademy/autograding-java-exercises
10
10
GRADING_REPO_PATH : grading-repo
11
11
STUDENT_REPO_PATH : student-repo
12
- MAX_TESTS : ${{ vars.MAX_TESTS || 42 }}
13
- TEST_SCORE_MARKS : ${{ vars.TEST_SCORE_MARKS || 70 }}
14
- AGENT_SCORE_MARKS : ${{ vars.AGENT_SCORE_MARKS || 30 }}
12
+ MAX_TESTS : 42
15
13
16
14
jobs :
17
15
autograding :
16
+ if : ${{ !github.event.repository.is_template && github.event.sender.type != 'Bot' }}
18
17
runs-on : ubuntu-latest
19
- if : github.event.sender.type != 'Bot'
20
18
steps :
21
19
- name : Checkout grading repo
22
20
uses : actions/checkout@v4
@@ -37,12 +35,12 @@ jobs:
37
35
uses : actions/setup-java@v4
38
36
with :
39
37
distribution : temurin
40
- java-version : ' 21 '
38
+ java-version : " 21 "
41
39
cache : maven
42
40
43
41
- uses : actions/setup-node@v3
44
42
with :
45
- node-version : ' 20 '
43
+ node-version : " 20 "
46
44
- run : npm install xml2js
47
45
48
46
- name : Extract modules from pom.xml
57
55
const parser = new xml2js.Parser();
58
56
const result = await parser.parseStringPromise(pomXml);
59
57
const modules = result.project.modules[0].module || [];
60
-
58
+
61
59
core.setOutput('modules', modules.join(' '));
62
60
63
61
return modules.join(' ');
@@ -297,67 +295,57 @@ jobs:
297
295
uses : actions/github-script@v7
298
296
with :
299
297
script : |
300
- // Helper function to parse result string
301
- function parseResult(result, scaleTo ) {
298
+ // Helper function to parse result string and scale to 5
299
+ function parseResult(result) {
302
300
const [earnedStr, totalStr] = result.replace(/^"|"$/g, '').split('/');
303
301
let earned = parseInt(earnedStr, 10);
304
302
let available = parseInt(totalStr, 10);
305
303
306
- if (scaleTo !== undefined && available > 0) {
307
- earned = Math.round((earned / available) * scaleTo);
308
- available = scaleTo;
304
+ if (available > 0) {
305
+ earned = Math.round((earned / available) * 5);
306
+ } else {
307
+ earned = 0;
309
308
}
310
-
311
- return { earned, available };
309
+ return earned;
312
310
}
313
311
314
- // Parse get-tests-score result (e.g., 34/42), scale to 70%
315
- const testsResult = '${{ steps.get-tests-score.outputs.result }}';
316
- const tests = parseResult(testsResult, ${{ env.TEST_SCORE_MARKS }});
317
- // Parse get-agent-score result (e.g., 27/100), scale to 30%
318
- const aiResult = '${{ steps.get-agent-score.outputs.result }}';
319
- const ai = parseResult(aiResult, ${{ env.AGENT_SCORE_MARKS }});
320
- // Sum
321
- const totalEarned = tests.earned + ai.earned;
322
- const totalAvailable = tests.available + ai.available;
323
-
324
- console.log(`Tests score: ${JSON.stringify(tests)}`);
325
- console.log(`Agent score: ${JSON.stringify(ai)}`);
326
- core.setOutput('earned', totalEarned);
327
- core.setOutput('available', totalAvailable);
312
+ // Parse get-tests-score and get-agent-score results (e.g., 34/42, 27/100)
313
+ const testsScore = parseResult('${{ steps.get-tests-score.outputs.result }}');
314
+ const agentScore = parseResult('${{ steps.get-agent-score.outputs.result }}');
315
+ // Total out of 10
316
+ const totalScore = testsScore + agentScore;
317
+
318
+ console.log(`Functionality (out of 5): ${testsScore}`);
319
+ console.log(`Code Quality (out of 5): ${agentScore}`);
320
+ console.log(`Total (out of 10): ${totalScore}`);
321
+ core.setOutput('functionality', testsScore);
322
+ core.setOutput('code_quality', agentScore);
323
+ core.setOutput('earned', totalScore);
324
+ core.setOutput('available', 10);
328
325
329
326
- name : Trigger Airtable update
330
327
uses : actions/github-script@v7
331
328
env :
332
329
GRADING_REPO : ${{ env.GRADING_REPO }}
333
330
REPO_URL : ${{ github.repository }}
334
- TEST_SCORE : ${{ steps.get-tests-score .outputs.passed }}
335
- AGENT_SCORE : ${{ steps.get-agent-score .outputs.result }}
331
+ TEST_SCORE : ${{ steps.aggregate-points .outputs.functionality }}
332
+ AGENT_SCORE : ${{ steps.aggregate-points .outputs.code_quality }}
336
333
with :
337
334
github-token : ${{ secrets.CLASSROOM_TOKEN }}
338
335
script : |
339
336
const [owner, repo] = process.env.GRADING_REPO.split('/');
340
-
341
- // Convert agent score from percentage to 0-5 scale
342
- const agentScorePercent = parseInt(process.env.AGENT_SCORE, 10);
343
- const agentScore = Math.round((agentScorePercent / 100) * 5);
344
-
345
- // Convert test score to 0-5 scale
346
- const testScore = Math.round((parseInt(process.env.TEST_SCORE, 10) / ${{ env.MAX_TESTS }}) * 5);
347
-
348
337
// Ensure repository URL has trailing slash
349
338
const repoUrl = process.env.REPO_URL.endsWith('/') ? process.env.REPO_URL : `${process.env.REPO_URL}/`;
350
-
351
339
try {
352
340
await github.rest.actions.createWorkflowDispatch({
353
341
owner,
354
342
repo,
355
343
workflow_id: 'update-airtable.yml',
356
344
ref: 'main',
357
345
inputs: {
358
- test_score: testScore.toString() ,
359
- agent_score: agentScore.toString() ,
360
- repository_url: repoUrl
346
+ test_score: process.env.TEST_SCORE ,
347
+ agent_score: process.env.AGENT_SCORE ,
348
+ repository_url: `https://github.com/${ repoUrl}`
361
349
}
362
350
});
363
351
console.log('Successfully triggered Airtable update workflow');
@@ -377,6 +365,8 @@ jobs:
377
365
EARNED : ${{ steps.aggregate-points.outputs.earned }}
378
366
AVAILABLE : ${{ steps.aggregate-points.outputs.available }}
379
367
BOT_USER : ${{ vars.PR_AGENT_BOT_USER }}
368
+ FUNCTIONALITY : ${{ steps.aggregate-points.outputs.functionality }}
369
+ CODE_QUALITY : ${{ steps.aggregate-points.outputs.code_quality }}
380
370
with :
381
371
github-token : ${{ secrets.CLASSROOM_TOKEN }}
382
372
script : |
@@ -396,10 +386,8 @@ jobs:
396
386
}
397
387
398
388
function calculateGrade(earned, available) {
399
- const earnedNum = parseInt(earned, 10);
400
- const availableNum = parseInt(available, 10);
401
- const percent = availableNum > 0 ? (earnedNum / availableNum) * 100 : 0;
402
-
389
+ // Calculate grade based on marks out of 10
390
+ const percent = available > 0 ? (earned / available) * 100 : 0;
403
391
if (percent >= 90) return 'A+';
404
392
if (percent >= 80) return 'A';
405
393
if (percent >= 70) return 'B';
@@ -409,33 +397,21 @@ jobs:
409
397
return 'Fail';
410
398
}
411
399
412
- function calculateFunctionalityScore(passed, total) {
413
- const passedNum = parseInt(passed, 10);
414
- const totalNum = parseInt(total, 10);
415
-
416
- return totalNum > 0 ? Math.round((passedNum / totalNum) * 100) : 0;
417
- }
418
-
419
- function formatBody({ score, comment, gradingRepo, gradingPrId, studentRepo, feedbackPr, passed, total, earned, available }) {
420
- const funcScore = calculateFunctionalityScore(passed, total);
400
+ function formatBody({ score, comment, gradingRepo, gradingPrId, studentRepo, feedbackPr, functionality, codeQuality, earned, available }) {
421
401
const grade = calculateGrade(earned, available);
422
402
let fixedBody = comment.replace(new RegExp(`${gradingRepo}/pull/${gradingPrId}`, 'g'), `${studentRepo}/pull/${feedbackPr}`);
423
403
424
404
fixedBody = fixedBody.replace(/PR Reviewer Guide/g, 'Exercise Status Report');
425
405
fixedBody = fixedBody.replace(/to aid the review process/g, 'on the current state of your exercise');
426
406
427
- const qualityScoreRowRegex = /<tr><td>🏅 <strong>Score<\/strong>: (\d+)<\/td><\/tr>/;
407
+ // Replace the score table with individual marks out of 5 and overall grade
408
+ const scoreTableRegex = /<tr><td>🏅 <strong>Score<\/strong>: (\d+)<\/td><\/tr>/;
428
409
const rowTemplate = (label, value) => `<tr><td>${label}: <strong>${value}</strong></td></tr>`;
429
- const match = fixedBody.match(qualityScoreRowRegex);
430
-
431
- if (match) {
432
- const qualityScore = match[1];
433
- const funcRow = rowTemplate('Functionality', `${funcScore}%`);
434
- const qualityRow = rowTemplate('Code Quality', `${qualityScore}%`);
435
- const gradeRow = rowTemplate('Overall Grade', grade);
410
+ const funcRow = rowTemplate('Functionality', `${functionality}/5`);
411
+ const qualityRow = rowTemplate('Code Quality', `${codeQuality}/5`);
412
+ const gradeRow = rowTemplate('Overall Grade', grade);
436
413
437
- fixedBody = fixedBody.replace(qualityScoreRowRegex, `${funcRow}\n${qualityRow}\n${gradeRow}`);
438
- }
414
+ fixedBody = fixedBody.replace(scoreTableRegex, `${funcRow}\n${qualityRow}\n${gradeRow}`);
439
415
440
416
return fixedBody;
441
417
}
@@ -460,10 +436,10 @@ jobs:
460
436
const gradingRepo = process.env.GRADING_REPO;
461
437
const gradingPrId = process.env.GRADING_PR_ID;
462
438
const studentRepo = process.env.STUDENT_REPO;
463
- const passed = process.env.PASSED ;
464
- const total = process.env.TOTAL ;
465
- const earned = process.env.EARNED;
466
- const available = process.env.AVAILABLE;
439
+ const functionality = parseFloat( process.env.FUNCTIONALITY) ;
440
+ const codeQuality = parseFloat( process.env.CODE_QUALITY) ;
441
+ const earned = parseFloat( process.env.EARNED) ;
442
+ const available = parseFloat( process.env.AVAILABLE) ;
467
443
const botUser = process.env.BOT_USER;
468
444
const [owner, repo] = gradingRepo.split('/');
469
445
const [studentOwner, studentRepoName] = studentRepo.split('/');
@@ -473,7 +449,6 @@ jobs:
473
449
feedbackPr = await getFeedbackPrNumber({ github, owner: studentOwner, repo: studentRepoName });
474
450
} catch (err) {
475
451
core.setFailed(`Failed to find feedback PR: ${err.message}`);
476
-
477
452
return;
478
453
}
479
454
@@ -483,17 +458,15 @@ jobs:
483
458
bodyRaw = await getBotCommentBody({ github, owner, repo, issue_number: gradingPrId, botUser });
484
459
} catch (err) {
485
460
core.setFailed(`Failed to fetch bot comment: ${err.message}`);
486
-
487
461
return;
488
462
}
489
463
490
464
let feedbackBody;
491
465
492
466
try {
493
- feedbackBody = formatBody({ score, comment: bodyRaw, gradingRepo, gradingPrId, studentRepo, feedbackPr, passed, total , earned, available });
467
+ feedbackBody = formatBody({ score, comment: bodyRaw, gradingRepo, gradingPrId, studentRepo, feedbackPr, functionality, codeQuality , earned, available });
494
468
} catch (err) {
495
469
core.setFailed(`Failed to format feedback body: ${err.message}`);
496
-
497
470
return;
498
471
}
499
472
@@ -508,7 +481,6 @@ jobs:
508
481
});
509
482
} catch (err) {
510
483
core.setFailed(`Failed to post feedback comment: ${err.message}`);
511
-
512
484
return;
513
485
}
514
486
0 commit comments