From db5658ccc188739d332f120a3e4bb2e55812bf39 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 6 Mar 2023 14:48:59 +0100 Subject: [PATCH] Distribute some traces to students --- ui/src/lib/components/TraceStatus.svelte | 15 ++++ ui/src/lib/works.js | 12 ++++ ui/src/routes/works/[wid]/+page.svelte | 21 +++++- works.go | 87 ++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 ui/src/lib/components/TraceStatus.svelte diff --git a/ui/src/lib/components/TraceStatus.svelte b/ui/src/lib/components/TraceStatus.svelte new file mode 100644 index 0000000..1374c74 --- /dev/null +++ b/ui/src/lib/components/TraceStatus.svelte @@ -0,0 +1,15 @@ + + +{#if status} + + {status} + +{/if} diff --git a/ui/src/lib/works.js b/ui/src/lib/works.js index c87dd0f..387d350 100644 --- a/ui/src/lib/works.js +++ b/ui/src/lib/works.js @@ -132,6 +132,18 @@ export class Work { } } + async getMyTraces() { + const res = await fetch(`api/works/${this.id}/traces`, { + method: 'GET', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } + } + async getGrades() { const res = await fetch(`api/works/${this.id}/grades`, { method: 'GET', diff --git a/ui/src/routes/works/[wid]/+page.svelte b/ui/src/routes/works/[wid]/+page.svelte index 9b8c7aa..ea83701 100644 --- a/ui/src/routes/works/[wid]/+page.svelte +++ b/ui/src/routes/works/[wid]/+page.svelte @@ -6,6 +6,7 @@ import ScoreBadge from '$lib/components/ScoreBadge.svelte'; import SubmissionStatus from '$lib/components/SubmissionStatus.svelte'; import SurveyBadge from '$lib/components/SurveyBadge.svelte'; + import TraceStatus from '$lib/components/TraceStatus.svelte'; import WorkAdmin from '$lib/components/WorkAdmin.svelte'; import WorkRepository from '$lib/components/WorkRepository.svelte'; import { getScore } from '$lib/users'; @@ -293,7 +294,25 @@ Note finale : {grade.score} {#if grade.comment}– {grade.comment}{/if} - {:catch error} + {#await w.getMyTraces()} +
+ {:then traces} + {#each traces as trace} +
+

{trace.title}

+ +
+
+
{#each trace.logs as l (l.pos)}{l.out}{/each}
+
+ {/each} + {:catch error} +
+ + {error.message} +
+ {/await} + {:catch error}
{error.message} diff --git a/works.go b/works.go index c6d76b5..f72498f 100644 --- a/works.go +++ b/works.go @@ -354,6 +354,12 @@ func declareAPIAdminWorksRoutes(router *gin.RouterGroup) { }) } +type UserTrace struct { + Title string `json:"title"` + Status string `json:"status"` + Logs []*drone.Line `json:"logs"` +} + func declareAPIAuthWorksRoutes(router *gin.RouterGroup) { worksRoutes := router.Group("/works/:wid") worksRoutes.Use(workHandler) @@ -380,6 +386,87 @@ func declareAPIAuthWorksRoutes(router *gin.RouterGroup) { } }) + worksRoutes.GET("/traces", func(c *gin.Context) { + u := c.MustGet("LoggedUser").(*User) + w := c.MustGet("work").(*Work) + + if !u.IsAdmin && !w.Corrected { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied"}) + } else if g, err := u.GetMyWorkGrade(w); err != nil && errors.Is(err, sql.ErrNoRows) { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Aucune note n'a été attribuée pour ce travail. Avez-vous rendu ce travail ?"}) + } else if err != nil { + log.Printf("Unable to GetMyWorkGrade(uid=%d;wid=%d): %s", u.Id, w.Id, err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during gradation."}) + } else { + repo, err := u.getRepositoryByWork(g.IdWork) + if err != nil { + log.Printf("Unable to getRepositoryByWork(uid=%d, wid=%d): %s", u.Id, g.IdWork, err.Error()) + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find a corresponding repository."}) + return + } + + slug := strings.Split(repo.TestsRef, "/") + if len(slug) < 3 { + return + } + + buildn, err := strconv.ParseInt(slug[2], 10, 32) + if err != nil { + return + } + + client := drone.NewClient(droneEndpoint, droneConfig) + build, err := client.Build(slug[0], slug[1], int(buildn)) + if err != nil { + log.Println("Unable to communicate with Drone:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to communicate with the gradation service"}) + return + } + + var traces []UserTrace + + for _, stage := range build.Stages { + for _, step := range stage.Steps { + if step.Name == "TP checks" || strings.HasPrefix(step.Name, "Test ") { + result, err := client.Logs(slug[0], slug[1], int(buildn), stage.Number, step.Number) + if err != nil { + log.Printf("An error occurs when retrieving logs from Drone (%s/%s/%d/%d/%d): %s", slug[0], slug[1], buildn, stage.Number, step.Number, err.Error()) + continue + } + + keeptLogs := []*drone.Line{} + for nline, line := range result { + // Infos about image, skip + if nline < 3 { + continue + } + if strings.HasPrefix(line.Message, "+ export GRADE") { + continue + } + if strings.HasPrefix(line.Message, "+ echo grade:") { + line.Message = "+ Your grade for this step is:\r\n" + } + + keeptLogs = append(keeptLogs, line) + } + + traces = append(traces, UserTrace{ + Title: step.Name, + Status: step.Status, + Logs: keeptLogs, + }) + } + } + } + + if traces == nil { + c.JSON(http.StatusOK, []interface{}{}) + } else { + c.JSON(http.StatusOK, traces) + } + } + }) + declareAPIAuthRepositoriesRoutes(worksRoutes) declareAPIWorkSubmissionsRoutes(worksRoutes) }