Skip to content

Commit

Permalink
merging updates for v2.9
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienjoly committed Jan 23, 2018
1 parent d2437ff commit 183b09f
Show file tree
Hide file tree
Showing 16 changed files with 5,039 additions and 79 deletions.
5 changes: 0 additions & 5 deletions .firebaserc

This file was deleted.

4,796 changes: 4,796 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@
"build": "node ./src/build-exercises.js",
"test": "node ./src/test-solutions.js",
"start": "npm run build && node src/server.js",
"deploy-firebase": "echo \"⚠ don't forget to rebuild and commit first!\" && PROJECT_ID=`node -e \"console.log(require('./exam-data/exam-config.js').backend.FIREBASE_CONFIG.projectId);\"` && echo Deploying to $PROJECT_ID... && firebase use $PROJECT_ID && firebase deploy; # may not scale over 20 simultaneous connections",
"deploy-heroku": "echo \"⚠ don't forget to rebuild and commit first!\" && PROJECT_ID=__TODO__ && echo Deploying to $PROJECT_ID... && heroku git:remote -a $PROJECT_ID && git push heroku `git rev-parse --abbrev-ref HEAD`:master",
"deploy-heroku-instances": "./src/deploy-heroku-instances.sh",
"deploy-firebase-instances": "./src/deploy-firebase-instances.sh",
"eval": "node ./src/evaluate.js",
"eval-students": "src/score-students.sh ./students/*.json",
"eval-firebase-export": "node ./src/evaluateGroupFile.js ../exam-data/js-qcm-ft-export-fast-track.json >./exam-data/js-qcm-ft-export-fast-track.eval.log",
"eval-student-groups": "src/score-student-groups.sh ./student-groups/*.json",
"eval-groups-from-firebase": "src/score-groups.sh",
"deploy-firebase": "echo \"don't forget to rebuild and commit first!\" && firebase use default && firebase deploy; # may not scale over 20 simultaneous connections",
"deploy-heroku": "echo \"don't forget to commit first!\" && heroku git:remote -a js-part-2 && git push heroku `git rev-parse --abbrev-ref HEAD`:master",
"deploy-instances": "./src/multi-instance-deploy.sh",
"eval-instances": "./src/multi-instance-eval.sh"
"eval-instances": "./src/eval-instances.sh",
"eval-firebase-dumps": "src/eval-firebase-dumps.sh ./exam-data/*.json",
"eval-student-submissions": "src/eval-student-submissions.sh ./students/*.json"
},
"author": "Adrien Joly <adrien.joly@gmail.com>",
"license": "MIT",
Expand Down
71 changes: 71 additions & 0 deletions sample-data/exam-config.multi-instance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Example of multi-instance configuration
// In order to use this config file, rename it to exam-config.js

const INSTANCE = process.env.JS_TEST_INSTANCE;
const LOCAL_TEST_MODE = typeof INSTANCE === 'undefined';

module.exports = {

// Instance names (i.e. one per Firebase project, with separate hosting and database)
instances: [ 'instance1', 'instance2', 'instance3' ], // => npm run deploy-firebase-instances

// Front-end config
title: 'JavaScript Exam (' + (LOCAL_TEST_MODE ? 'DEFAULT' : INSTANCE) + ')',

// General settings
PUBLIC_TEST_MODE: true, // set to false to restrict acccess and identify students using Google Login
DISPLAY_SOLUTIONS_AFTER_SUBMIT: true, // set to false, for real exams

redirectToHttps: false,

// Settings for conversion and publication of exercise templates
examPack: {
publishSolutions: true, // `true` required for realtime-eval/auto-eval back-ends, DISPLAY_SOLUTIONS_AFTER_SUBMIT and/or dashboard
publishEvalTests: LOCAL_TEST_MODE, // `true` required for realtime-eval/auto-eval back-ends, DISPLAY_SOLUTIONS_AFTER_SUBMIT and/or dashboard
},

// Back-end config
backend: {
type: LOCAL_TEST_MODE ? 'auto-eval' : 'firebase',
/*
EMAIL_SUBMIT_CONFIG: {
mdTemplate: readfile('public/data/submitted.md'),
},
*/
FIREBASE_CONFIG: {
'instance1': {
apiKey: "kjgkerjghkrjghkerjg-kjgeklrja",
databaseURL: "https://jsexam-1.firebaseio.com",
messagingSenderId: "847593487934867"
},
'instance2': {
apiKey: "kjgkerjghkrjghkerjg-kjgeklrjb",
databaseURL: "https://jsexam-2.firebaseio.com",
messagingSenderId: "847593487934868"
},
'instance3': {
apiKey: "kjgkerjghkrjghkerjg-kjgeklrjc",
databaseURL: "https://jsexam-3.firebaseio.com",
messagingSenderId: "847593487934869"
},
}[ LOCAL_TEST_MODE ? 'instance1' : INSTANCE ],
},

teacherEmail: 'teacher.email@domain.com', // required for dashboard auth

// Authentication
GOOGLE_CLIENT_ID: '7465834756-rkjgelkhjlerkgjlerkgjelrkg.apps.googleusercontent.com', // generated from https://console.developers.google.com/apis/credentials?project=eemi-own-exam&authuser=1
GOOGLE_CLIENT_DOMAIN: 'domain.com', // to restrict access to users from a certain domain only
LOGIN_INVITE: 'Connect using your Google for Education account:',

// Evaluation / grading
quizzGrading: {
ptsRight: 1,
ptsWrong: 0, // or -0.5 for example
ptsNull: 0,
},
codeGrading: {
ptsPerExercise: 1,
}

};
8 changes: 7 additions & 1 deletion src/StudentEvaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,13 @@ function evaluateStudent(student, next) {
console.log('=> TOTAL STUDENT SCORE:', totalScore, '/', totalPoints);
var csv = [ student.key, totalScore ];
fs.appendFileSync(SCORES_FILE, csv.toString() + '\n');
fs.appendFile(SCORES_DETAIL_FILE, csv.concat(scoreArray).toString() + '\n', next);
fs.appendFile(SCORES_DETAIL_FILE, csv.concat(scoreArray).toString() + '\n', function() {
next(null, {
studentKey: student.key,
studentTotalScore: totalScore,
totalPoints: totalPoints
});
});
}

function evaluateExercise(evaluator, callback) {
Expand Down
File renamed without changes.
24 changes: 24 additions & 0 deletions src/eval-firebase-dumps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

# This script evaluates students from Firebase JSON exports, to log and csv formats.
# (one student grade per line)

# Usage: ./eval-firebase-dumps.sh ./exam-data/*.json

rm ./exam-data/score*.* &>/dev/null

for FILEPATH in $*;
do
FILENAME=$(basename "$FILEPATH")
EVAL_DIR="${FILENAME%.*}"
EVAL_PATH=exam-data/$EVAL_DIR

echo "* Evaluating $FILEPATH to $EVAL_PATH/ ..."
mkdir $EVAL_PATH &>/dev/null
node ./src/evaluateGroupFile.js ".$FILEPATH" &>$EVAL_PATH/eval.log
./src/split-eval-log-per-student.sh $EVAL_PATH/eval.log >/dev/null
mv Eval_*.txt $EVAL_PATH/
mv exam-data/score*.* $EVAL_PATH/
done;

echo "✅ Done!"
32 changes: 32 additions & 0 deletions src/eval-instances.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

# This script evaluates students from Firebase databases (1 instance per class),
# to log and csv formats (1 student grade per line).

CONFIG_FILE=./exam-data/exam-config.js

INSTANCES=()
ARRAY=`node -e "console.log(require('$CONFIG_FILE').instances.join(';'));"`
IFS=';' read -ra INSTANCES <<< "$ARRAY"

echo "* Cleaning up and rebuilding eval testers ..."
rm ./exam-data/score*.* &>/dev/null
npm run build >/dev/null

for INSTANCE in "${INSTANCES[@]}"
do
INST_PATH=exam-data/$INSTANCE
DATABASE_URL=`JS_TEST_INSTANCE=$INSTANCE node -e "console.log(require('$CONFIG_FILE').backend.FIREBASE_CONFIG.databaseURL);"`

echo "* Evaluating students from instance $INSTANCE to $INST_PATH ..."
mkdir $INST_PATH &>/dev/null
JS_TEST_INSTANCE=$INSTANCE npm run eval >$INST_PATH/eval.log
# (or) JS_TEST_INSTANCE=$INSTANCE node ./src/evaluateGroupFile.js ../exam-data/$INSTANCE.json >$INST_PATH/eval.log

./src/split-eval-log-per-student.sh $INST_PATH/eval.log >/dev/null
mv Eval_*.txt $INST_PATH/
mv exam-data/score*.* $INST_PATH/
done

echo "✅ Done!"

22 changes: 22 additions & 0 deletions src/eval-student-submissions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

# This script evaluates students from email submissions, to log and csv formats.
# (one student grade per line)

# Usage: ./eval-student-submissions.sh ./students/*.json

rm ./exam-data/score*.* &>/dev/null

for FILEPATH in $*;
do
EVAL_PATH=exam-data/email-submissions
FILENAME=$(basename "$FILEPATH")
STUDENT_NAME="${FILENAME%.*}"

echo "* Evaluating $FILEPATH to $EVAL_PATH/ ..."
mkdir $EVAL_PATH &>/dev/null
node ./src/evaluateStudentFile.js ".$FILEPATH" &>$EVAL_PATH/Eval_$STUDENT_NAME.txt
done;

mv exam-data/score*.* $EVAL_PATH/
echo "✅ Done!"
31 changes: 27 additions & 4 deletions src/evaluateGroupFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,41 @@ var PATH_SOURCE = './exam-data/';
var SCORES_FILE = PATH_SOURCE + 'scores.csv';
var SCORES_DETAIL_FILE = PATH_SOURCE + 'scores-detail.csv';

function appendScoreValues(array) {
var csvLine = array.toString() + '\n';
fs.appendFileSync(SCORES_FILE, csvLine);
fs.appendFileSync(SCORES_DETAIL_FILE, csvLine);
}

function sum(a, b) {
return a + b;
}

function median(arr){
arr = arr.sort(function(a, b){ return a - b; });
var i = arr.length / 2;
return i % 1 == 0 ? (arr[i - 1] + arr[i]) / 2 : arr[Math.floor(i)];
}

console.log('Reading and evaluating answers from:', filePath, '...');

var groupHeader = [ '\"GROUP FILE:\"', '\"' + filePath + '\"' ].toString() + '\n';
fs.appendFileSync(SCORES_FILE, groupHeader);
fs.appendFileSync(SCORES_DETAIL_FILE, groupHeader);
var groupHeader = [ '\"GROUP FILE:\"', '\"' + filePath + '\"' ];
appendScoreValues(groupHeader);

var submissionSet = require(filePath).submissions; // this line allows to parse an entire firebase json export at once

var submissions = Object.keys(submissionSet).map(function(key){
return _.extend(submissionSet[key], { key: key });
});

async.mapSeries(submissions, evaluateStudent, function(){
async.mapSeries(submissions, evaluateStudent, function(err, res){
if (err) throw err;
var flatScores = res.map(function(student) {
return student.studentTotalScore;
});
[
[ '(AVERAGE)', flatScores.reduce(sum) / res.length ],
[ '(MEDIAN)', median(flatScores) ]
].map(appendScoreValues);
process.exit();
});
44 changes: 44 additions & 0 deletions src/multi-firebase-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

# (forked from https://github.com/adrienjoly/js-exam/blob/master/deploy.sh)

# This script deploys this app to the following *.firebaseapp.com subdomains:
instances=( jsp1-cl1 jsp1-cl2 jsp1-cl3 )

# ...by temporarily altering the corresponding mentions to js-exam in the following files:
files=( ./exam-data/exam-config.js )

# initial mentions to be replaced:
initial="__INSTANCE__"

for instance in "${instances[@]}"
do
echo "Deploying to instance: $instance .firebaseapp.com ..."

for f in "${files[@]}"
do
echo "- replacing mentions of $initial in file: $f"
mv $f $f.bak
replacement="s/$initial/$instance/g"
sed $replacement $f.bak > $f
done

npm run build
git status -s

# Push them to Firebase hosting, then repent of the commit (cf http://rhodesmill.org/brandon/2012/quietly-pushing-to-heroku/)
#git add .
git commit -am 'Temporary Firebase-only deployment commit'
firebase use $instance && firebase deploy
git reset --soft HEAD~1

for f in "${files[@]}"
do
echo "- restoring mentions to $instance in file: $f"
rm $f
mv $f.bak $f
done

done

npm run build
21 changes: 0 additions & 21 deletions src/multi-instance-eval.sh

This file was deleted.

17 changes: 0 additions & 17 deletions src/score-groups.sh

This file was deleted.

13 changes: 0 additions & 13 deletions src/score-student-groups.sh

This file was deleted.

10 changes: 0 additions & 10 deletions src/score-students.sh

This file was deleted.

9 changes: 9 additions & 0 deletions src/split-eval-log-per-student.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

# Splits a given eval.log file to 1 file per student (./Eval_<student_name>.txt).

perl -pwe '
if (/^STUDENT: (.+)/) {
open $out, ">", "Eval_$1.txt" or die $!;
select $out;
}' $1

0 comments on commit 183b09f

Please sign in to comment.