diff options
Diffstat (limited to 'js')
| -rw-r--r-- | js/config.js | 7 | ||||
| -rw-r--r-- | js/database.js | 10 | ||||
| -rw-r--r-- | js/graduation-common.js | 161 | ||||
| -rw-r--r-- | js/mock.js | 6 | ||||
| -rw-r--r-- | js/student-common.js | 22 |
5 files changed, 171 insertions, 35 deletions
diff --git a/js/config.js b/js/config.js index f07c5ec..5fc456c 100644 --- a/js/config.js +++ b/js/config.js @@ -3,6 +3,12 @@ function getCourseId() { return courseId; } +// TODO(muhq): retrieve when loading activities/DBs +const cockpidId = 93017047; +function getCockpitId() { + return cockpidId; +} + function getCourseParticipantsUrl() { return `https://lernplattform.bycs.de/user/index.php?id=${courseId}`; } @@ -17,6 +23,7 @@ async function getUserIconUrl(userId) { export { getCourseId, + getCockpitId, getUserIconUrl, getCourseParticipantsUrl }; diff --git a/js/database.js b/js/database.js index 7fd3d47..73c2d17 100644 --- a/js/database.js +++ b/js/database.js @@ -29,7 +29,7 @@ async function populateStudentDbs() { const courseDoc = new DOMParser().parseFromString(await courseRes.text(), 'text/html'); let dbs = new Map(); - for (const link of courseDoc.querySelectorAll("div.activityname > a.aalink")) { + for (const link of courseDoc.querySelectorAll("div.activityname a.aalink")) { const match = link.textContent.match(/(\d+)(Shadow|Public|Submit)/); if (!match) continue; const studentId = match[1]; @@ -82,7 +82,13 @@ async function loadFromDatabaseActivity(id, params) { let entry = {}; row.querySelectorAll('.db-cell').forEach(field => { const key = field.dataset.key; - const value = field.innerText.trim(); + let value; + // Moodle includes the full date in the title attribute of the included span + if (key === "date") { + value = field.querySelector("span").title; + } else { + value = field.innerText.trim(); + } entry[key] = value; }); data.push(entry); diff --git a/js/graduation-common.js b/js/graduation-common.js index 3ab1fdd..1c68f38 100644 --- a/js/graduation-common.js +++ b/js/graduation-common.js @@ -22,11 +22,15 @@ class GraduationChange { publicDBFields () { return { "change": this.change, + "date": this.date, }; } dbFields () { return { + // We include the date in the json because Moodle reports the timeadded + // locale dependent. + "date": this.date, "change": this.change, "reason": this.reason, "context": this.context, @@ -34,9 +38,25 @@ class GraduationChange { } static fromDBEntry(e) { - const {date, coach, json} = e; + const {coach, json} = e; const change = JSON.parse(json); - return new GraduationChange(new Date(date), change.change, change.reason, coach, change.context); + return new GraduationChange(new Date(change.date), change.change, change.reason, coach, change.context); + } + + static newObservationRequest() { + return new GraduationChange(new Date(), "ObservationRequest", "", "", ""); + } + + static newObservationStart() { + return new GraduationChange(new Date(), "ObservationStart", "", "", ""); + } + + static newObservationEnd(reason, success) { + return new GraduationChange(new Date(), "ObservationEnd", reason, "", JSON.stringify(success)); + } + + static newObservationChange(observation) { + return new GraduationChange(observation.date, "Observation", "", "", JSON.stringify(observation.status)); } static newGreenPointChange(reason, coach) { @@ -53,14 +73,16 @@ class GraduationChange { } class Graduation { - constructor(student, green, orange, red, level, stickers, observation, changes) { + constructor(student, green, orange, red, level, stickers, observationRequested, observation, pastObservations, changes) { this.student = student; this.green = green; this.orange = orange; this.red = red; this.level = level; this.stickers = stickers; + this.observationRequested = observationRequested; this.observation = observation; + this.pastObservations = pastObservations; this.changes = changes; student.graduation = this; @@ -71,41 +93,66 @@ class Graduation { let orange = 0; let red = 0; let level = 1; + let openObservationRequest = null; let observationPeriod = null; let pastObservations = []; let stickers = []; + + // The parsing logic assumes that changes are ordered chronologically + changes.sort((a, b) => a.date - b.date); + for (const change of changes) { switch (change.change) { case "GrĂ¼n": green++; break; + case "Orange": orange++; break; + case "Rot": red++; break; + + case "ObservationRequest": + openObservationRequest = change.date; + break; + case "ObservationStart": - observationPeriod = new ObservationPeriod(change.date, null, []); + console.assert(openObservationRequest, "No request for observation start"); + observationPeriod = new ObservationPeriod(change.date); + openObservationRequest = null; break; + case "ObservationEnd": - observationPeriod.end = change.date; + console.assert(observationPeriod, "No active observation period"); + observationPeriod.finishPeriod(change.context.success); + if (change.context.success) { + level++; + } pastObservations.push(observationPeriod); observationPeriod = null; break; + case "Observation": - const observation = Observation.fromContext(change.context); - if (observationPeriod) { - observationPeriod.addObservation(observation); - } else { - alert(`No active observation period`); - } + console.assert(observationPeriod, "No active observation period"); + const observation = Observation.fromChange(change); + observationPeriod.addObservation(observation); + break; + + case "LevelChange": + level = change.context.level; break; + default: alert(`Unexpected change: ${change}`); } } - return new Graduation(student, green, orange, red, level, stickers, observationPeriod, changes); + return new Graduation(student, green, orange, red, level, + stickers, + openObservationRequest, observationPeriod, pastObservations, + changes); } addChange(change) { @@ -120,7 +167,36 @@ class Graduation { * Request observation */ async requestObservation() { - await addEntryToStudentDatabase(this.student, "submit", {change: "ObservationRequest"}); + const request = GraduationChange.newObservationRequest(); + this.observationRequested = request.date; + await addEntryToStudentDatabase(this.student, "submit", JSON.stringify(request.publicDBFields())); + } + + /** + * Start an observation + */ + async startObservation() { + if (!this.observationRequested) { + console.assert(this.observationRequested, "No observation requested"); + return; + } + this.observationRequested = null; + this.observation = new ObservationPeriod(new Date()); + return submitGraduationChange(this.student, GraduationChange.newObservationStart()); + } + + /** + * End an observation + */ + async endObservation(reason, success) { + if (!this.observation) { + console.assert(this.observation, "No active observation"); + return; + } + this.observation.end = new Date(); + this.pastObservations.push(this.observation); + this.observation = null; + return submitGraduationChange(this.student, GraduationChange.newObservationEnd(reason, success)); } /** @@ -147,10 +223,14 @@ class Graduation { } class ObservationPeriod { - constructor(start, end, observations) { + constructor(start) { this.start = start; - this.end = end; - this.observations = observations; + this.observations = []; + } + + finishPeriod(success) { + this.end = new Date(); + this.success = success; } ended() { @@ -158,18 +238,28 @@ class ObservationPeriod { } ready() { - // TODO(muhq): add time contraint - return this.observations > 10; + if (this.observations.length < 10) + return false; + + // at least 10 observation on different days + let days = new Map(); + for (const observation of this.observations) { + const d = observation.date; + days.set(`${d.getFullYear()}${d.getMonth()}${d.getDate()}`, true); + } + return days.size >= 10; } getObservationsSums() { - let sums = Array(OBSERVATION_TYPES.length).fill({"yes":0, "no":0}); + let sums = Array(OBSERVATION_TYPES.length).fill().map(() => ({"yes":0, "no":0})); for (const observation of this.observations) { for (let i = 0; i < sums.length; i++) { - if (observation.status[i]) - sums[i].yes++; - else - sums[i].no++; + if (observation.status[i]) { + if (observation.status[i] === "yes") + sums[i].yes++; + else + sums[i].no++; + } } } return sums; @@ -192,10 +282,14 @@ const OBSERVATION_TYPES = [ class Observation { constructor(date, status) { - console.assert(status.length != OBSERVATION_TYPES.length, "invalid observation status"); + console.assert(status.length == OBSERVATION_TYPES.length, "invalid observation status"); this.date = date; this.status = status; } + + static fromChange(change) { + return new Observation(change.date, JSON.parse(change.context)) + } } async function fetchGraduationChangesFromDb(student, type) { @@ -206,15 +300,23 @@ async function fetchGraduationChangesFromDb(student, type) { const gradChange = GraduationChange.fromDBEntry(e); changes.push(gradChange); } - return changes; + return changes.flat(); } async function fetchGraduationChanges(student) { - return fetchGraduationChangesFromDb(student, "shadow"); + let changes = await Promise.all([ + fetchGraduationChangesFromDb(student, "shadow"), + fetchGraduationChangesFromDb(student, "submit"), + ]); + return changes.flat(); } async function fetchPublicGraduationChanges(student) { - return fetchGraduationChangesFromDb(student, "public"); + let changes = await Promise.all([ + fetchGraduationChangesFromDb(student, "public"), + fetchGraduationChangesFromDb(student, "submit"), + ]); + return changes.flat(); } async function fetchGraduation(student) { @@ -248,8 +350,9 @@ async function submitGraduationChange(student, change) { } async function submitObservation(student, observation) { - student.graduation.addObservation(observation); - await addEntryToStudentDatabase(student, "shadow", JSON.stringify(observation)); + console.assert(student.graduation.observation, "no active observation"); + student.graduation.observation.addObservation(observation); + await submitGraduationChange(student, GraduationChange.newObservationChange(observation)); } export { @@ -83,13 +83,13 @@ function fetchPublicGraduationMock(s) { } function submitGraduationChangeMock(s, c) { - assert(s.graduation, "no graduation") + console.assert(s.graduation, "no graduation") s.graduation.addChange(c); } function submitObservationMock(s, o) { - assert(s.graduation, "no graduation") - assert(s.graduation.observation, "no observation period") + console.assert(s.graduation, "no graduation") + console.assert(s.graduation.observation, "no observation period") s.graduation.observation.addObservation(o); } diff --git a/js/student-common.js b/js/student-common.js index c89b818..778ff8d 100644 --- a/js/student-common.js +++ b/js/student-common.js @@ -1,4 +1,4 @@ -import { getUserIconUrl, getCourseParticipantsUrl } from "./config.js"; +import { getCourseId, getUserIconUrl, getCourseParticipantsUrl } from "./config.js"; import { fetchGraduation } from "./graduation-common.js"; /** * Class to represent Students. @@ -27,6 +27,26 @@ class Student { } return this.graduation; } + + static async fetchById(id) { + let name = ""; + let group = ""; + + const res = await fetch(`/user/view.php?id=${id}&course=${getCourseId()}`); + const doc = new DOMParser().parseFromString(await res.text(), "text/html"); + + name = doc.querySelector("div.page-header-headings > h2").textContent; + + const cards = doc.querySelectorAll("div.card-body dl"); + for (const card of cards) { + if (card.querySelector("dt").textContent !== "Gruppe") + continue; + group = card.querySelector("dd > a").textContent; + break; + } + + return new Student(id, name, group); + } } async function fetchStudent(idFilter, nameFilter, groupFilter) { |
