====== Classroom 50: Noten nach Moodle übertragen ====== Der Übertrag läuft **zentral** aus dem ''classroom50''-Config-Repo und **nachts**, nicht bei jedem Push. ===== Warum zentral und nachts ===== Der Moodle-Webservice-Token kann Noten für **beliebige Personen** setzen. In einem Studi-Repo liest ihn ein selbst hinzugefügter Workflow in drei Zeilen aus. Genau so lief es unter GitHub Classroom (''secrets: inherit'' in ''classroom.yml'') — diese Exposition endet mit der Migration. Technisch geht es unter Classroom 50 auch gar nicht anders: Der zentrale Reusable Workflow ''autograde-runner.yaml'' deklariert nur ''outputs:'' und reicht **keine Secrets** an den Bewertungs-Job durch. Nachts statt bei jedem Push, weil der direkte Weg einen Token in jedem Studi-Repo, eine eigene Workflow-Datei je Repo, eine nachgebaute Skip-Logik und rund 33 % mehr Actions-Minuten gekostet hätte — und ein Nacht-Fallback wäre trotzdem nötig gewesen. ===== Voraussetzungen auf der Moodle-Seite ===== - Eine Aktivität vom Typ **External Assignment** (''mod_externalassignment''). - Ihr Name ist **exakt der Slug** der Aufgabe, z.B. ''m323-lu01-a02-imperativer-ggt''. - Der **Moodle-Benutzername entspricht dem GitHub-Login** der lernenden Person. - Die Person ist im Kurs eingeschrieben. ''No matching assignment found. Contact your teacher.'' heisst **nicht** zwingend, dass die Aktivität fehlt. Das Plugin löst ''(assignmentname, username)'' gemeinsam auf und meldet denselben Text, wenn nur der **Benutzer** im Kurs fehlt. Zum Testen deshalb einen echten Studi-Login nehmen — der eigene Lehrer-Account ist im Kurs meist kein Teilnehmer. ===== Zugangsdaten ===== Im **classroom50-Repo**, nicht in der Studi-Organisation: ^ Art ^ Name ^ Wert ^ | Secret | ''MOODLE_TOKEN'' | Webservice-Token | | Variable | ''MOODLE_URL'' | z.B. ''https://moodle.bzz.ch'' | | Variable | ''MOODLE_FUNCTION'' | optional, Vorgabe ''mod_externalassignment_update_grade'' | gh secret set MOODLE_TOKEN --repo /classroom50 gh variable set MOODLE_URL --repo /classroom50 --body "https://moodle.bzz.ch" Für den Token einen **eigenen Webservice-Benutzer** anlegen, der nur diese eine Funktion darf. Je enger die Rechte, desto kleiner der Schaden bei einem Leck. ===== Was übertragen wird ===== Je Kombination aus Aufgabe und Person geht die **neueste** Abgabe raus. ^ Moodle-Feld ^ Quelle ^ | ''assignment_name'' | Slug der Aufgabe aus ''scores.json'' | | ''user_name'' | GitHub-Login der besitzenden Person | | ''points'' / ''max'' | Punkte der neuesten Abgabe | | ''externallink'' | URL des GitHub-Releases | | ''feedback'' | Release-Text; davor ein Hinweis bei verspäteter Abgabe, dahinter der Link zur Abgabe | ===== Zeitplan ===== ^ Job ^ Zeit ^ Was er tut ^ | ''collect-scores'' | nachts | sammelt alle Releases in ''/scores.json'' | | ''moodle-sync'' | ''57 4 * * *'' (04:57) | überträgt daraus nach Moodle | 40 Minuten Abstand, damit ''collect-scores'' sicher durch ist. Ein Zustandsfile ''/moodle-state.json'' merkt sich, was schon übertragen wurde. Übersprungen wird nur, wenn Abgabe **und** Punktzahl identisch sind — eine Nachbewertung derselben Abgabe geht also erneut raus. ===== Von Hand auslösen ===== Im classroom50-Repo unter **Actions → Moodle Sync → Run workflow**. Eingaben: Klassenzimmer, optional Aufgabe und Login, dazu die Schalter ''dry_run'' und ''force''. Bleibt das Klassenzimmer leer, laufen **alle** Ordner mit einer ''scores.json''. gh workflow run moodle-sync.yaml --repo /classroom50 \ -f classroom= -f dry_run=true Trockenlauf-Ausgabe: Moodle-Übertrag: 2 Abgaben [dry-run] / anna: 5/7 [dry-run] / bruno: 7/7 übertragen: 2 | unverändert: 0 | fehlgeschlagen: 0 ===== Lokal prüfen, ohne Zugangsdaten ===== gh api repos//classroom50/contents//scores.json \ -H 'Accept: application/vnd.github.raw' > scores.json GH_TOKEN=$(gh auth token) python -m pygrader50.moodle scores.json --dry-run Alle Optionen: [[.:cli|CLI-Referenz]]. ===== Probleme ===== ^ Meldung / Symptom ^ Ursache ^ | ''No matching assignment found'' | Aktivitätsname **oder** Kursmitgliedschaft passt nicht | | ''keine XML-Antwort erhalten'' | falsche ''MOODLE_URL'' oder ungültiger Token — Moodle liefert eine Login-Seite | | Note kommt nicht an, Log sagt „unverändert" | Zustandsfile hält sie für erledigt — mit ''force'' erneut auslösen | | Nachtlauf jede Nacht rot, immer dieselbe Person | Karteileichen-Eintrag in ''scores.json'', siehe [[.:lehrpersonen|Betriebsfallen]] |