From 14ccdd22a4f7306091ba51f464934b160d8f0214 Mon Sep 17 00:00:00 2001 From: Oliver Braun Date: Sun, 19 Jul 2026 18:38:24 +0200 Subject: [PATCH] feat: live progress on the repos overview page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repos page did a blocking SSR GitLab call — a blank page for seconds. Switch it to the browser WebSocket subscription courseRepoOverviewProgress: a spinner plus a "prüfe GitLab … x/total" counter and a progress bar, with each assignment card appearing as soon as it is checked. $lib/reposSubscription wraps the stream (like checkSubscription); the load now only passes route params. A token error shows the /token link. Co-Authored-By: Claude Opus 4.8 (1M context) --- schema.graphql | 19 ++++++ src/lib/gql/gql.ts | 12 ++-- src/lib/gql/graphql.ts | 16 ++--- src/lib/reposSubscription.ts | 50 ++++++++++++++ .../courses/[name]/repos/+page.server.ts | 36 ++-------- src/routes/courses/[name]/repos/+page.svelte | 68 ++++++++++++++++--- 6 files changed, 146 insertions(+), 55 deletions(-) create mode 100644 src/lib/reposSubscription.ts diff --git a/schema.graphql b/schema.graphql index 2d94a90..e6c4749 100644 --- a/schema.graphql +++ b/schema.graphql @@ -616,6 +616,25 @@ extend type Query { courseRepoOverview(course: String!): [AssignmentRepos!]! } +""" +One streamed item of a course repo overview: a completed assignment as it is +checked, then a final event with `done: true`. `error` is set (with done) when the +overview cannot start — e.g. no stored GitLab token. +""" +type RepoOverviewEvent { + "The assignment just checked (null on the final done event)." + assignment: AssignmentRepos + "Total number of assignments to check (for a progress count)." + total: Int! + done: Boolean! + error: String +} + +extend type Subscription { + "Stream the repo overview of one of the caller's courses assignment by assignment, so the GUI shows progress. Needs a stored GitLab token." + courseRepoOverviewProgress(course: String!): RepoOverviewEvent! +} + # ----- scheduling.graphqls ----- "The lifecycle state of a scheduled job. It starts PENDING, is claimed to RUNNING, and ends in exactly one terminal state." enum JobStatus { diff --git a/src/lib/gql/gql.ts b/src/lib/gql/gql.ts index 4235d69..d7b301f 100644 --- a/src/lib/gql/gql.ts +++ b/src/lib/gql/gql.ts @@ -18,6 +18,7 @@ type Documents = { "\n\tsubscription CourseCheckProgress($name: String!) {\n\t\tcourseCheckProgress(name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\tresult {\n\t\t\t\tcourse\n\t\t\t\terrors\n\t\t\t\tok\n\t\t\t\tstudents {\n\t\t\t\t\tinput\n\t\t\t\t\tstatus\n\t\t\t\t\tmessage\n\t\t\t\t}\n\t\t\t\tgroups {\n\t\t\t\t\tname\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tinput\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tmessage\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tduplicates {\n\t\t\t\t\tstudent\n\t\t\t\t\tgroups\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": typeof types.CourseCheckProgressDocument, "\n\tsubscription RunOp($token: String!, $confirmPhrase: String) {\n\t\trunOp(token: $token, confirmPhrase: $confirmPhrase) {\n\t\t\tlevel\n\t\t\ttext\n\t\t}\n\t}\n": typeof types.RunOpDocument, "\n\tsubscription AssignmentReportProgress($course: String!, $name: String!) {\n\t\tassignmentReportProgress(course: $course, name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\treport {\n\t\t\t\tcourse\n\t\t\t\tassignment\n\t\t\t\turl\n\t\t\t\tdescription\n\t\t\t\tgenerated\n\t\t\t\thasReleaseMergeRequest\n\t\t\t\thasReleaseDockerImages\n\t\t\t\tprojects {\n\t\t\t\t\tname\n\t\t\t\t\tactive\n\t\t\t\t\temptyRepo\n\t\t\t\t\tcommits\n\t\t\t\t\tlastActivity\n\t\t\t\t\twebUrl\n\t\t\t\t\topenIssuesCount\n\t\t\t\t\topenMergeRequestsCount\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tusername\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\tlastCommit {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tcommitterName\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\trelease {\n\t\t\t\t\t\tmergeRequest {\n\t\t\t\t\t\t\tfound\n\t\t\t\t\t\t\twebUrl\n\t\t\t\t\t\t\tpipelineStatus\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdockerImages {\n\t\t\t\t\t\t\tstatus\n\t\t\t\t\t\t\timages {\n\t\t\t\t\t\t\t\twanted\n\t\t\t\t\t\t\t\timage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": typeof types.AssignmentReportProgressDocument, + "\n\tsubscription CourseRepoOverviewProgress($course: String!) {\n\t\tcourseRepoOverviewProgress(course: $course) {\n\t\t\ttotal\n\t\t\tdone\n\t\t\terror\n\t\t\tassignment {\n\t\t\t\tname\n\t\t\t\tper\n\t\t\t\ttargets\n\t\t\t\texisting\n\t\t\t\tnote\n\t\t\t\trepos {\n\t\t\t\t\tfor\n\t\t\t\t\turl\n\t\t\t\t\texists\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": typeof types.CourseRepoOverviewProgressDocument, "\n\t\t\t\tquery Me {\n\t\t\t\t\tme {\n\t\t\t\t\t\temail\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.MeDocument, "\n\t\t\t\tquery ServerInfo {\n\t\t\t\t\tserverInfo {\n\t\t\t\t\t\tversion\n\t\t\t\t\t\tcommit\n\t\t\t\t\t\tdate\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.ServerInfoDocument, "\n\t\t\tmutation CopyAssignment($course: String!, $from: String!, $newName: String!) {\n\t\t\t\tcopyAssignment(course: $course, from: $from, newName: $newName) {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t}\n\t\t": typeof types.CopyAssignmentDocument, @@ -43,7 +44,6 @@ type Documents = { "\n\t\t\t\tquery CourseLint($name: String!) {\n\t\t\t\t\tcourseLint(name: $name) {\n\t\t\t\t\t\tpath\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\tseverity\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.CourseLintDocument, "\n\t\t\t\tquery CourseActivity($name: String!) {\n\t\t\t\t\tcourseActivity(course: $name) {\n\t\t\t\t\t\tassignment\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.CourseActivityDocument, "\n\t\t\t\tquery AssignmentEditor($course: String!, $name: String!) {\n\t\t\t\t\tcourse(name: $course) {\n\t\t\t\t\t\tassignmentNames\n\t\t\t\t\t}\n\t\t\t\t\tassignmentSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdeprecated\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbranchRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tapprovalSettingsSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tapprovalRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tassignment(course: $course, name: $name) {\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tname\n\t\t\t\t\t\textends\n\t\t\t\t\t\textendsOptions\n\t\t\t\t\t\tabstract\n\t\t\t\t\t\town {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolved\n\t\t\t\t\t\tresolveError\n\t\t\t\t\t}\n\t\t\t\t\tassignmentUrls(course: $course, name: $name) {\n\t\t\t\t\t\tper\n\t\t\t\t\t\tgroupUrl\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignmentActivity(course: $course, name: $name) {\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.AssignmentEditorDocument, - "\n\t\t\t\tquery CourseRepoOverview($course: String!) {\n\t\t\t\t\tcourseRepoOverview(course: $course) {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tper\n\t\t\t\t\t\ttargets\n\t\t\t\t\t\texisting\n\t\t\t\t\t\tnote\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\texists\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.CourseRepoOverviewDocument, "\n\t\t\t\tquery CourseStudents($course: String!) {\n\t\t\t\t\tcourseStudents(course: $course) {\n\t\t\t\t\t\temail\n\t\t\t\t\t\tfound\n\t\t\t\t\t\tfirstName\n\t\t\t\t\t\tlastName\n\t\t\t\t\t\tgender\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tmtknr\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.CourseStudentsDocument, "\n\t\t\t\tquery CourseYAML($name: String!) {\n\t\t\t\t\tcourseYAML(name: $name)\n\t\t\t\t}\n\t\t\t": typeof types.CourseYamlDocument, "\n\t\t\t\tquery ScheduledJobs {\n\t\t\t\t\tscheduledJobs {\n\t\t\t\t\t\tid\n\t\t\t\t\t\top\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tassignment\n\t\t\t\t\t\trunAt\n\t\t\t\t\t\tgraceMinutes\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tcreatedAt\n\t\t\t\t\t\tstartedAt\n\t\t\t\t\t\tfinishedAt\n\t\t\t\t\t\terr\n\t\t\t\t\t\tparams {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": typeof types.ScheduledJobsDocument, @@ -54,6 +54,7 @@ const documents: Documents = { "\n\tsubscription CourseCheckProgress($name: String!) {\n\t\tcourseCheckProgress(name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\tresult {\n\t\t\t\tcourse\n\t\t\t\terrors\n\t\t\t\tok\n\t\t\t\tstudents {\n\t\t\t\t\tinput\n\t\t\t\t\tstatus\n\t\t\t\t\tmessage\n\t\t\t\t}\n\t\t\t\tgroups {\n\t\t\t\t\tname\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tinput\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tmessage\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tduplicates {\n\t\t\t\t\tstudent\n\t\t\t\t\tgroups\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": types.CourseCheckProgressDocument, "\n\tsubscription RunOp($token: String!, $confirmPhrase: String) {\n\t\trunOp(token: $token, confirmPhrase: $confirmPhrase) {\n\t\t\tlevel\n\t\t\ttext\n\t\t}\n\t}\n": types.RunOpDocument, "\n\tsubscription AssignmentReportProgress($course: String!, $name: String!) {\n\t\tassignmentReportProgress(course: $course, name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\treport {\n\t\t\t\tcourse\n\t\t\t\tassignment\n\t\t\t\turl\n\t\t\t\tdescription\n\t\t\t\tgenerated\n\t\t\t\thasReleaseMergeRequest\n\t\t\t\thasReleaseDockerImages\n\t\t\t\tprojects {\n\t\t\t\t\tname\n\t\t\t\t\tactive\n\t\t\t\t\temptyRepo\n\t\t\t\t\tcommits\n\t\t\t\t\tlastActivity\n\t\t\t\t\twebUrl\n\t\t\t\t\topenIssuesCount\n\t\t\t\t\topenMergeRequestsCount\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tusername\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\tlastCommit {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tcommitterName\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\trelease {\n\t\t\t\t\t\tmergeRequest {\n\t\t\t\t\t\t\tfound\n\t\t\t\t\t\t\twebUrl\n\t\t\t\t\t\t\tpipelineStatus\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdockerImages {\n\t\t\t\t\t\t\tstatus\n\t\t\t\t\t\t\timages {\n\t\t\t\t\t\t\t\twanted\n\t\t\t\t\t\t\t\timage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": types.AssignmentReportProgressDocument, + "\n\tsubscription CourseRepoOverviewProgress($course: String!) {\n\t\tcourseRepoOverviewProgress(course: $course) {\n\t\t\ttotal\n\t\t\tdone\n\t\t\terror\n\t\t\tassignment {\n\t\t\t\tname\n\t\t\t\tper\n\t\t\t\ttargets\n\t\t\t\texisting\n\t\t\t\tnote\n\t\t\t\trepos {\n\t\t\t\t\tfor\n\t\t\t\t\turl\n\t\t\t\t\texists\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n": types.CourseRepoOverviewProgressDocument, "\n\t\t\t\tquery Me {\n\t\t\t\t\tme {\n\t\t\t\t\t\temail\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.MeDocument, "\n\t\t\t\tquery ServerInfo {\n\t\t\t\t\tserverInfo {\n\t\t\t\t\t\tversion\n\t\t\t\t\t\tcommit\n\t\t\t\t\t\tdate\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.ServerInfoDocument, "\n\t\t\tmutation CopyAssignment($course: String!, $from: String!, $newName: String!) {\n\t\t\t\tcopyAssignment(course: $course, from: $from, newName: $newName) {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t}\n\t\t": types.CopyAssignmentDocument, @@ -79,7 +80,6 @@ const documents: Documents = { "\n\t\t\t\tquery CourseLint($name: String!) {\n\t\t\t\t\tcourseLint(name: $name) {\n\t\t\t\t\t\tpath\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\tseverity\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.CourseLintDocument, "\n\t\t\t\tquery CourseActivity($name: String!) {\n\t\t\t\t\tcourseActivity(course: $name) {\n\t\t\t\t\t\tassignment\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.CourseActivityDocument, "\n\t\t\t\tquery AssignmentEditor($course: String!, $name: String!) {\n\t\t\t\t\tcourse(name: $course) {\n\t\t\t\t\t\tassignmentNames\n\t\t\t\t\t}\n\t\t\t\t\tassignmentSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdeprecated\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbranchRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tapprovalSettingsSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tapprovalRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tassignment(course: $course, name: $name) {\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tname\n\t\t\t\t\t\textends\n\t\t\t\t\t\textendsOptions\n\t\t\t\t\t\tabstract\n\t\t\t\t\t\town {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolved\n\t\t\t\t\t\tresolveError\n\t\t\t\t\t}\n\t\t\t\t\tassignmentUrls(course: $course, name: $name) {\n\t\t\t\t\t\tper\n\t\t\t\t\t\tgroupUrl\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignmentActivity(course: $course, name: $name) {\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.AssignmentEditorDocument, - "\n\t\t\t\tquery CourseRepoOverview($course: String!) {\n\t\t\t\t\tcourseRepoOverview(course: $course) {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tper\n\t\t\t\t\t\ttargets\n\t\t\t\t\t\texisting\n\t\t\t\t\t\tnote\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\texists\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.CourseRepoOverviewDocument, "\n\t\t\t\tquery CourseStudents($course: String!) {\n\t\t\t\t\tcourseStudents(course: $course) {\n\t\t\t\t\t\temail\n\t\t\t\t\t\tfound\n\t\t\t\t\t\tfirstName\n\t\t\t\t\t\tlastName\n\t\t\t\t\t\tgender\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tmtknr\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.CourseStudentsDocument, "\n\t\t\t\tquery CourseYAML($name: String!) {\n\t\t\t\t\tcourseYAML(name: $name)\n\t\t\t\t}\n\t\t\t": types.CourseYamlDocument, "\n\t\t\t\tquery ScheduledJobs {\n\t\t\t\t\tscheduledJobs {\n\t\t\t\t\t\tid\n\t\t\t\t\t\top\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tassignment\n\t\t\t\t\t\trunAt\n\t\t\t\t\t\tgraceMinutes\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tcreatedAt\n\t\t\t\t\t\tstartedAt\n\t\t\t\t\t\tfinishedAt\n\t\t\t\t\t\terr\n\t\t\t\t\t\tparams {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t": types.ScheduledJobsDocument, @@ -116,6 +116,10 @@ export function graphql(source: "\n\tsubscription RunOp($token: String!, $confir * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n\tsubscription AssignmentReportProgress($course: String!, $name: String!) {\n\t\tassignmentReportProgress(course: $course, name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\treport {\n\t\t\t\tcourse\n\t\t\t\tassignment\n\t\t\t\turl\n\t\t\t\tdescription\n\t\t\t\tgenerated\n\t\t\t\thasReleaseMergeRequest\n\t\t\t\thasReleaseDockerImages\n\t\t\t\tprojects {\n\t\t\t\t\tname\n\t\t\t\t\tactive\n\t\t\t\t\temptyRepo\n\t\t\t\t\tcommits\n\t\t\t\t\tlastActivity\n\t\t\t\t\twebUrl\n\t\t\t\t\topenIssuesCount\n\t\t\t\t\topenMergeRequestsCount\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tusername\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\tlastCommit {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tcommitterName\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\trelease {\n\t\t\t\t\t\tmergeRequest {\n\t\t\t\t\t\t\tfound\n\t\t\t\t\t\t\twebUrl\n\t\t\t\t\t\t\tpipelineStatus\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdockerImages {\n\t\t\t\t\t\t\tstatus\n\t\t\t\t\t\t\timages {\n\t\t\t\t\t\t\t\twanted\n\t\t\t\t\t\t\t\timage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n"): (typeof documents)["\n\tsubscription AssignmentReportProgress($course: String!, $name: String!) {\n\t\tassignmentReportProgress(course: $course, name: $name) {\n\t\t\tmessage\n\t\t\tdone\n\t\t\terror\n\t\t\treport {\n\t\t\t\tcourse\n\t\t\t\tassignment\n\t\t\t\turl\n\t\t\t\tdescription\n\t\t\t\tgenerated\n\t\t\t\thasReleaseMergeRequest\n\t\t\t\thasReleaseDockerImages\n\t\t\t\tprojects {\n\t\t\t\t\tname\n\t\t\t\t\tactive\n\t\t\t\t\temptyRepo\n\t\t\t\t\tcommits\n\t\t\t\t\tlastActivity\n\t\t\t\t\twebUrl\n\t\t\t\t\topenIssuesCount\n\t\t\t\t\topenMergeRequestsCount\n\t\t\t\t\tmembers {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tusername\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\tlastCommit {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tcommitterName\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\twebUrl\n\t\t\t\t\t}\n\t\t\t\t\trelease {\n\t\t\t\t\t\tmergeRequest {\n\t\t\t\t\t\t\tfound\n\t\t\t\t\t\t\twebUrl\n\t\t\t\t\t\t\tpipelineStatus\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdockerImages {\n\t\t\t\t\t\t\tstatus\n\t\t\t\t\t\t\timages {\n\t\t\t\t\t\t\t\twanted\n\t\t\t\t\t\t\t\timage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n\tsubscription CourseRepoOverviewProgress($course: String!) {\n\t\tcourseRepoOverviewProgress(course: $course) {\n\t\t\ttotal\n\t\t\tdone\n\t\t\terror\n\t\t\tassignment {\n\t\t\t\tname\n\t\t\t\tper\n\t\t\t\ttargets\n\t\t\t\texisting\n\t\t\t\tnote\n\t\t\t\trepos {\n\t\t\t\t\tfor\n\t\t\t\t\turl\n\t\t\t\t\texists\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n"): (typeof documents)["\n\tsubscription CourseRepoOverviewProgress($course: String!) {\n\t\tcourseRepoOverviewProgress(course: $course) {\n\t\t\ttotal\n\t\t\tdone\n\t\t\terror\n\t\t\tassignment {\n\t\t\t\tname\n\t\t\t\tper\n\t\t\t\ttargets\n\t\t\t\texisting\n\t\t\t\tnote\n\t\t\t\trepos {\n\t\t\t\t\tfor\n\t\t\t\t\turl\n\t\t\t\t\texists\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -216,10 +220,6 @@ export function graphql(source: "\n\t\t\t\tquery CourseActivity($name: String!) * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n\t\t\t\tquery AssignmentEditor($course: String!, $name: String!) {\n\t\t\t\t\tcourse(name: $course) {\n\t\t\t\t\t\tassignmentNames\n\t\t\t\t\t}\n\t\t\t\t\tassignmentSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdeprecated\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbranchRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tapprovalSettingsSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tapprovalRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tassignment(course: $course, name: $name) {\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tname\n\t\t\t\t\t\textends\n\t\t\t\t\t\textendsOptions\n\t\t\t\t\t\tabstract\n\t\t\t\t\t\town {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolved\n\t\t\t\t\t\tresolveError\n\t\t\t\t\t}\n\t\t\t\t\tassignmentUrls(course: $course, name: $name) {\n\t\t\t\t\t\tper\n\t\t\t\t\t\tgroupUrl\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignmentActivity(course: $course, name: $name) {\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t"): (typeof documents)["\n\t\t\t\tquery AssignmentEditor($course: String!, $name: String!) {\n\t\t\t\t\tcourse(name: $course) {\n\t\t\t\t\t\tassignmentNames\n\t\t\t\t\t}\n\t\t\t\t\tassignmentSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tgroup\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdeprecated\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbranchRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tapprovalSettingsSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t\toptions {\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\tlabel\n\t\t\t\t\t\t\tdescription\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tapprovalRuleSchema {\n\t\t\t\t\t\tkey\n\t\t\t\t\t\tlabel\n\t\t\t\t\t\tdescription\n\t\t\t\t\t\tkind\n\t\t\t\t\t\trequired\n\t\t\t\t\t\texample\n\t\t\t\t\t}\n\t\t\t\t\tassignment(course: $course, name: $name) {\n\t\t\t\t\t\tcourse\n\t\t\t\t\t\tname\n\t\t\t\t\t\textends\n\t\t\t\t\t\textendsOptions\n\t\t\t\t\t\tabstract\n\t\t\t\t\t\town {\n\t\t\t\t\t\t\tkey\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolved\n\t\t\t\t\t\tresolveError\n\t\t\t\t\t}\n\t\t\t\t\tassignmentUrls(course: $course, name: $name) {\n\t\t\t\t\t\tper\n\t\t\t\t\t\tgroupUrl\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignmentActivity(course: $course, name: $name) {\n\t\t\t\t\t\top\n\t\t\t\t\t\tstatus\n\t\t\t\t\t\tdetail\n\t\t\t\t\t\tat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n\t\t\t\tquery CourseRepoOverview($course: String!) {\n\t\t\t\t\tcourseRepoOverview(course: $course) {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tper\n\t\t\t\t\t\ttargets\n\t\t\t\t\t\texisting\n\t\t\t\t\t\tnote\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\texists\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t"): (typeof documents)["\n\t\t\t\tquery CourseRepoOverview($course: String!) {\n\t\t\t\t\tcourseRepoOverview(course: $course) {\n\t\t\t\t\t\tname\n\t\t\t\t\t\tper\n\t\t\t\t\t\ttargets\n\t\t\t\t\t\texisting\n\t\t\t\t\t\tnote\n\t\t\t\t\t\trepos {\n\t\t\t\t\t\t\tfor\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\texists\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/lib/gql/graphql.ts b/src/lib/gql/graphql.ts index 53fa801..c5cc74c 100644 --- a/src/lib/gql/graphql.ts +++ b/src/lib/gql/graphql.ts @@ -108,6 +108,13 @@ export type AssignmentReportProgressSubscriptionVariables = Exact<{ export type AssignmentReportProgressSubscription = { assignmentReportProgress: { message: string, done: boolean, error: string | null, report: { course: string, assignment: string, url: string, description: string, generated: string | null, hasReleaseMergeRequest: boolean, hasReleaseDockerImages: boolean, projects: Array<{ name: string, active: boolean, emptyRepo: boolean, commits: number, lastActivity: string | null, webUrl: string, openIssuesCount: number, openMergeRequestsCount: number, members: Array<{ name: string, username: string, webUrl: string }>, lastCommit: { title: string, committerName: string, committedDate: string | null, webUrl: string } | null, release: { mergeRequest: { found: boolean, webUrl: string, pipelineStatus: string } | null, dockerImages: { status: string, images: Array<{ wanted: string, image: string | null }> } | null } | null }> } | null } }; +export type CourseRepoOverviewProgressSubscriptionVariables = Exact<{ + course: string; +}>; + + +export type CourseRepoOverviewProgressSubscription = { courseRepoOverviewProgress: { total: number, done: boolean, error: string | null, assignment: { name: string, per: string, targets: number, existing: number, note: string | null, repos: Array<{ for: string, url: string, exists: boolean }> } | null } }; + export type MeQueryVariables = Exact<{ [key: string]: never; }>; @@ -304,13 +311,6 @@ export type AssignmentEditorQueryVariables = Exact<{ export type AssignmentEditorQuery = { course: { assignmentNames: Array } | null, assignmentSchema: Array<{ key: string, label: string, description: string, group: string, kind: FieldKind, required: boolean, deprecated: boolean, example: string | null, options: Array<{ value: string, label: string, description: string }> }>, branchRuleSchema: Array<{ key: string, label: string, description: string, kind: FieldKind, required: boolean, example: string | null }>, approvalSettingsSchema: Array<{ key: string, label: string, description: string, kind: FieldKind, required: boolean, example: string | null, options: Array<{ value: string, label: string, description: string }> }>, approvalRuleSchema: Array<{ key: string, label: string, description: string, kind: FieldKind, required: boolean, example: string | null }>, assignment: { course: string, name: string, extends: string | null, extendsOptions: Array, abstract: boolean, resolved: string, resolveError: string | null, own: Array<{ key: string, value: string }> } | null, assignmentUrls: { per: string, groupUrl: string, repos: Array<{ for: string, url: string }> } | null, assignmentActivity: Array<{ op: string, status: string, detail: string, at: string }> }; -export type CourseRepoOverviewQueryVariables = Exact<{ - course: string; -}>; - - -export type CourseRepoOverviewQuery = { courseRepoOverview: Array<{ name: string, per: string, targets: number, existing: number, note: string | null, repos: Array<{ for: string, url: string, exists: boolean }> }> }; - export type CourseStudentsQueryVariables = Exact<{ course: string; }>; @@ -340,6 +340,7 @@ export const AuthCheckDocument = {"kind":"Document","definitions":[{"kind":"Oper export const CourseCheckProgressDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"CourseCheckProgress"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseCheckProgress"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"done"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"result"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"}},{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"ok"}},{"kind":"Field","name":{"kind":"Name","value":"students"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"input"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"input"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"duplicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"student"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const RunOpDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"RunOp"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"confirmPhrase"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runOp"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"confirmPhrase"},"value":{"kind":"Variable","name":{"kind":"Name","value":"confirmPhrase"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"text"}}]}}]}}]} as unknown as DocumentNode; export const AssignmentReportProgressDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"AssignmentReportProgress"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignmentReportProgress"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"done"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"report"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"}},{"kind":"Field","name":{"kind":"Name","value":"assignment"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"generated"}},{"kind":"Field","name":{"kind":"Name","value":"hasReleaseMergeRequest"}},{"kind":"Field","name":{"kind":"Name","value":"hasReleaseDockerImages"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"emptyRepo"}},{"kind":"Field","name":{"kind":"Name","value":"commits"}},{"kind":"Field","name":{"kind":"Name","value":"lastActivity"}},{"kind":"Field","name":{"kind":"Name","value":"webUrl"}},{"kind":"Field","name":{"kind":"Name","value":"openIssuesCount"}},{"kind":"Field","name":{"kind":"Name","value":"openMergeRequestsCount"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"webUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastCommit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"committerName"}},{"kind":"Field","name":{"kind":"Name","value":"committedDate"}},{"kind":"Field","name":{"kind":"Name","value":"webUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"release"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mergeRequest"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"found"}},{"kind":"Field","name":{"kind":"Name","value":"webUrl"}},{"kind":"Field","name":{"kind":"Name","value":"pipelineStatus"}}]}},{"kind":"Field","name":{"kind":"Name","value":"dockerImages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"wanted"}},{"kind":"Field","name":{"kind":"Name","value":"image"}}]}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const CourseRepoOverviewProgressDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"CourseRepoOverviewProgress"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseRepoOverviewProgress"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"done"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"per"}},{"kind":"Field","name":{"kind":"Name","value":"targets"}},{"kind":"Field","name":{"kind":"Name","value":"existing"}},{"kind":"Field","name":{"kind":"Name","value":"note"}},{"kind":"Field","name":{"kind":"Name","value":"repos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"for"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"exists"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const MeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const ServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"commit"}},{"kind":"Field","name":{"kind":"Name","value":"date"}}]}}]}}]} as unknown as DocumentNode; export const CopyAssignmentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CopyAssignment"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"from"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"copyAssignment"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}},{"kind":"Argument","name":{"kind":"Name","value":"from"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"Argument","name":{"kind":"Name","value":"newName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; @@ -365,7 +366,6 @@ export const CourseDocument = {"kind":"Document","definitions":[{"kind":"Operati export const CourseLintDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CourseLint"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseLint"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"severity"}}]}}]}}]} as unknown as DocumentNode; export const CourseActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CourseActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseActivity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment"}},{"kind":"Field","name":{"kind":"Name","value":"op"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"detail"}},{"kind":"Field","name":{"kind":"Name","value":"at"}}]}}]}}]} as unknown as DocumentNode; export const AssignmentEditorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AssignmentEditor"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignmentNames"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignmentSchema"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"group"}},{"kind":"Field","name":{"kind":"Name","value":"kind"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"deprecated"}},{"kind":"Field","name":{"kind":"Name","value":"example"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"branchRuleSchema"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"kind"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"example"}}]}},{"kind":"Field","name":{"kind":"Name","value":"approvalSettingsSchema"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"kind"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"example"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"approvalRuleSchema"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"kind"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"example"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignment"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"extends"}},{"kind":"Field","name":{"kind":"Name","value":"extendsOptions"}},{"kind":"Field","name":{"kind":"Name","value":"abstract"}},{"kind":"Field","name":{"kind":"Name","value":"own"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"resolved"}},{"kind":"Field","name":{"kind":"Name","value":"resolveError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignmentUrls"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"per"}},{"kind":"Field","name":{"kind":"Name","value":"groupUrl"}},{"kind":"Field","name":{"kind":"Name","value":"repos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"for"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignmentActivity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"op"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"detail"}},{"kind":"Field","name":{"kind":"Name","value":"at"}}]}}]}}]} as unknown as DocumentNode; -export const CourseRepoOverviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CourseRepoOverview"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseRepoOverview"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"per"}},{"kind":"Field","name":{"kind":"Name","value":"targets"}},{"kind":"Field","name":{"kind":"Name","value":"existing"}},{"kind":"Field","name":{"kind":"Name","value":"note"}},{"kind":"Field","name":{"kind":"Name","value":"repos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"for"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"exists"}}]}}]}}]}}]} as unknown as DocumentNode; export const CourseStudentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CourseStudents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"course"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseStudents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course"},"value":{"kind":"Variable","name":{"kind":"Name","value":"course"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"found"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"group"}},{"kind":"Field","name":{"kind":"Name","value":"mtknr"}}]}}]}}]} as unknown as DocumentNode; export const CourseYamlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CourseYAML"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"courseYAML"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}]}]}}]} as unknown as DocumentNode; export const ScheduledJobsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ScheduledJobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scheduledJobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"op"}},{"kind":"Field","name":{"kind":"Name","value":"course"}},{"kind":"Field","name":{"kind":"Name","value":"assignment"}},{"kind":"Field","name":{"kind":"Name","value":"runAt"}},{"kind":"Field","name":{"kind":"Name","value":"graceMinutes"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"err"}},{"kind":"Field","name":{"kind":"Name","value":"params"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/src/lib/reposSubscription.ts b/src/lib/reposSubscription.ts new file mode 100644 index 0000000..2efe4d1 --- /dev/null +++ b/src/lib/reposSubscription.ts @@ -0,0 +1,50 @@ +import { graphql } from '$lib/gql'; +import { subscribe } from '$lib/wsClient'; +import type { CourseRepoOverviewProgressSubscription } from '$lib/gql/graphql'; + +// One progress event of the repo-overview stream (the subscription payload). +export type RepoOverviewEvent = + CourseRepoOverviewProgressSubscription['courseRepoOverviewProgress']; + +const DOC = graphql(` + subscription CourseRepoOverviewProgress($course: String!) { + courseRepoOverviewProgress(course: $course) { + total + done + error + assignment { + name + per + targets + existing + note + repos { + for + url + exists + } + } + } + } +`); + +/** + * Subscribe to the live repo-overview stream. Calls `next` for every event + * (one finished assignment, then a final done) and `error` on a transport failure. + * Returns a cleanup function. + */ +export function subscribeRepoOverview( + course: string, + handlers: { next: (e: RepoOverviewEvent) => void; error: (message: string) => void } +): () => void { + return subscribe( + DOC, + { course }, + { + next: (d) => { + if (d.courseRepoOverviewProgress) handlers.next(d.courseRepoOverviewProgress); + }, + error: handlers.error + } + ); +} diff --git a/src/routes/courses/[name]/repos/+page.server.ts b/src/routes/courses/[name]/repos/+page.server.ts index b8e8635..84a28d2 100644 --- a/src/routes/courses/[name]/repos/+page.server.ts +++ b/src/routes/courses/[name]/repos/+page.server.ts @@ -1,38 +1,10 @@ -import { graphql } from '$lib/gql'; -import { backendRequest } from '$lib/server/backend'; -import { gqlErrorMessage } from '$lib/gqlError'; import type { PageServerLoad } from './$types'; /** - * Übersicht der tatsächlich generierten Repos pro Assignment. Fragt GitLab - * serverseitig ab (ein Gruppen-Listing pro Assignment) — kann ein paar Sekunden - * dauern und braucht einen hinterlegten GitLab-Token; ein Fehler (z. B. kein Token) - * wird als `error` durchgereicht statt die Seite scheitern zu lassen. + * Die Repo-Übersicht wird live im Browser über eine WebSocket-Subscription geladen + * (assignmentweise, mit Fortschritt), nicht im SSR — so blockiert die Seite nicht, + * während GitLab abgefragt wird. Der Load reicht nur die Route-Parameter durch. */ export const load: PageServerLoad = async ({ params }) => { - const course = params.name; - try { - const d = await backendRequest( - graphql(` - query CourseRepoOverview($course: String!) { - courseRepoOverview(course: $course) { - name - per - targets - existing - note - repos { - for - url - exists - } - } - } - `), - { course } - ); - return { course, overview: d?.courseRepoOverview ?? [], error: null as string | null }; - } catch (e) { - return { course, overview: null, error: gqlErrorMessage(e) }; - } + return { course: params.name }; }; diff --git a/src/routes/courses/[name]/repos/+page.svelte b/src/routes/courses/[name]/repos/+page.svelte index 1e58dad..d90525f 100644 --- a/src/routes/courses/[name]/repos/+page.svelte +++ b/src/routes/courses/[name]/repos/+page.svelte @@ -1,13 +1,50 @@ @@ -25,8 +62,21 @@ (GitLab-Token nötig).

+
+ {#if !done} + + prüfe GitLab … {checked}{total ? `/${total}` : ''} Assignments + {:else if !error} + ✓ fertig ({checked} Assignments) + {/if} +
+ + {#if total > 0 && !done} + + {/if} + {#if error} -
+
{error}
{#if needsToken} @@ -34,11 +84,11 @@ GitLab-Token hinterlegen →

{/if} - {:else if overview.length === 0} -

Keine Assignments in diesem Kurs.

- {:else} + {/if} + + {#if sorted.length > 0}
- {#each overview as a (a.name)} + {#each sorted as a (a.name)} {@const miss = missing(a)}
@@ -49,7 +99,7 @@ {a.name} {#if a.note} - + {a.note} {:else}