Distribute some traces to students
Some checks are pending
continuous-integration/drone/push Build is running
Some checks are pending
continuous-integration/drone/push Build is running
This commit is contained in:
parent
018ed9227f
commit
db5658ccc1
15
ui/src/lib/components/TraceStatus.svelte
Normal file
15
ui/src/lib/components/TraceStatus.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
export let status = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if status}
|
||||||
|
<span
|
||||||
|
class="badge"
|
||||||
|
class:bg-success={status == "success"}
|
||||||
|
class:bg-danger={status == "failure" || status == "killed"}
|
||||||
|
class:bg-warning={status == "pending" || status == "running"}
|
||||||
|
class:bg-dark={status != "success" && status != "failure" && status != "killed" && status != "pending" && status != "running"}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
{/if}
|
@ -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() {
|
async getGrades() {
|
||||||
const res = await fetch(`api/works/${this.id}/grades`, {
|
const res = await fetch(`api/works/${this.id}/grades`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import ScoreBadge from '$lib/components/ScoreBadge.svelte';
|
import ScoreBadge from '$lib/components/ScoreBadge.svelte';
|
||||||
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
|
import TraceStatus from '$lib/components/TraceStatus.svelte';
|
||||||
import WorkAdmin from '$lib/components/WorkAdmin.svelte';
|
import WorkAdmin from '$lib/components/WorkAdmin.svelte';
|
||||||
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
||||||
import { getScore } from '$lib/users';
|
import { getScore } from '$lib/users';
|
||||||
@ -293,7 +294,25 @@
|
|||||||
<i class="bi bi-clipboard2-check-fill text-info me-1"></i>
|
<i class="bi bi-clipboard2-check-fill text-info me-1"></i>
|
||||||
<strong>Note finale :</strong> <span title="Établie le {grade.date}">{grade.score}</span> {#if grade.comment}– {grade.comment}{/if}
|
<strong>Note finale :</strong> <span title="Établie le {grade.date}">{grade.score}</span> {#if grade.comment}– {grade.comment}{/if}
|
||||||
</div>
|
</div>
|
||||||
{:catch error}
|
{#await w.getMyTraces()}
|
||||||
|
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||||
|
{:then traces}
|
||||||
|
{#each traces as trace}
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<h4>{trace.title}</h4>
|
||||||
|
<TraceStatus status={trace.status} />
|
||||||
|
</div>
|
||||||
|
<div class="bg-dark text-light px-2 pt-2">
|
||||||
|
<pre class="pb-2">{#each trace.logs as l (l.pos)}{l.out}{/each}</pre>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:catch error}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||||
|
<strong>{error.message}</strong>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{:catch error}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||||
<strong>{error.message}</strong>
|
<strong>{error.message}</strong>
|
||||||
|
87
works.go
87
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) {
|
func declareAPIAuthWorksRoutes(router *gin.RouterGroup) {
|
||||||
worksRoutes := router.Group("/works/:wid")
|
worksRoutes := router.Group("/works/:wid")
|
||||||
worksRoutes.Use(workHandler)
|
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)
|
declareAPIAuthRepositoriesRoutes(worksRoutes)
|
||||||
declareAPIWorkSubmissionsRoutes(worksRoutes)
|
declareAPIWorkSubmissionsRoutes(worksRoutes)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user