admin: Better identify tries on exercice page
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
nemunaire 2025-03-30 15:20:11 +02:00
parent 38e3a4efdf
commit f6713c768b
6 changed files with 177 additions and 3 deletions

View file

@ -36,9 +36,16 @@ func declareExercicesRoutes(router *gin.RouterGroup) {
apiExercicesRoutes.POST("/diff-sync", APIDiffExerciceWithRemote)
apiExercicesRoutes.GET("/history.json", getExerciceHistory)
apiExercicesRoutes.GET("/stats.json", getExerciceStats)
apiExercicesRoutes.GET("/history.json", getExerciceHistory)
apiExercicesRoutes.GET("/tries", listTries)
apiTriesRoutes := apiExercicesRoutes.Group("/tries/:trid")
apiTriesRoutes.Use(ExerciceTryHandler)
apiTriesRoutes.GET("", getExerciceTry)
apiTriesRoutes.DELETE("", deleteExerciceTry)
apiHistoryRoutes := apiExercicesRoutes.Group("/history.json")
apiHistoryRoutes.Use(AssigneeCookieHandler)
@ -1619,3 +1626,58 @@ func APIDiffExerciceWithRemote(c *gin.Context) {
c.JSON(http.StatusOK, diffs)
}
func listTries(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
tries, err := exercice.TriesList()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, tries)
}
func ExerciceTryHandler(c *gin.Context) {
trid, err := strconv.ParseInt(string(c.Params.ByName("trid")), 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid try identifier"})
return
}
exercice := c.MustGet("exercice").(*fic.Exercice)
try, err := exercice.GetTry(trid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Try not found"})
return
}
c.Set("try", try)
c.Next()
}
func getExerciceTry(c *gin.Context) {
try := c.MustGet("try").(*fic.ExerciceTry)
err := try.FillDetails()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, try)
}
func deleteExerciceTry(c *gin.Context) {
try := c.MustGet("try").(*fic.ExerciceTry)
_, err := try.Delete()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.Status(http.StatusNoContent)
}

View file

@ -320,6 +320,9 @@ angular.module("FICApp")
.factory("ExerciceHistory", function ($resource) {
return $resource("api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
})
.factory("ExerciceTries", function ($resource) {
return $resource("api/exercices/:exerciceId/tries/:tryId", { exerciceId: '@idExercice', tryId: '@id' })
})
.factory("ExercicesStats", function ($resource) {
return $resource("api/exercices_stats.json", { themeId: '@id' })
})
@ -2117,6 +2120,10 @@ angular.module("FICApp")
}
})
.controller("SearchTryController", function ($scope, ExerciceTries) {
$scope.tr = ExerciceTries.get({ exerciceId: $scope.exercice.id, tryId: $scope.row.secondary });
})
.controller("SubmissionsStatsController", function ($scope, $http, $interval) {
var refresh = function () {
$http({

View file

@ -83,6 +83,7 @@
<div ng-controller="ExerciceFlagStatsController" ng-init="init(flag)">
<strong>Statistiques</strong>
<ul>
<li>ID: {{ flag.id }}</li>
<li>Validés: {{ stats["completed"] }}</li>
<li>
Tentés: {{ stats["tries"] }}
@ -188,6 +189,7 @@
<div ng-controller="ExerciceMCQStatsController" ng-init="init(q)">
<strong>Statistiques</strong>
<ul>
<li>ID: {{ q.id }}</li>
<li>Validés: {{ stats["completed"] }}</li>
<li>
Tentés: {{ stats["tries"] }}

View file

@ -347,7 +347,8 @@
<a href="exercices/{{ row.primary }}#quizz-{{ row.secondary }}" ng-if="row.kind == 'mcq_found'">{{ row.secondary_title }}</a>
<a href="exercices/{{ row.primary }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
</span>
<span ng-if="!row.secondary_title && row.secondary && row.kind != 'solved'">: {{ row.secondary }}</span>
<span ng-if="!row.secondary_title && row.secondary && row.kind != 'solved' && row.kind != 'tries'">: {{ row.secondary }}</span>
<span ng-if="!row.secondary_title && row.secondary && row.kind == 'tries'" ng-controller="SearchTryController"><br><span ng-repeat="line in tr.details"><span ng-if="!$first">, </span>{{ line.kind }}<span ng-if="line.related">#{{ line.related }}</span></span></span>
</td>
<td style="vertical-align: middle; padding: 0; background-color: {{ row.team_color }}" ng-show="logged">
<button type="button" data-toggle="modal" data-target="#updHistory" ng-if="row.kind != 'flag_found' && row.kind != 'tries' && row.kind != 'mcq_found'" data-idteam="{{ row.team_id }}" data-kind="{{ row.kind }}" data-time="{{ row.time }}" data-secondary="{{ row.secondary }}" data-coeff="{{ row.coefficient }}" class="float-right btn btn-sm btn-info"><span class="glyphicon glyphicon-edit" aria-hidden="true"></span></button>

View file

@ -11,7 +11,7 @@ import (
func (e *Exercice) GetHistory() ([]map[string]interface{}, error) {
hist := make([]map[string]interface{}, 0)
if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, 0, id_exercice, NULL, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION
if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, 0, id_exercice, id_try, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION
SELECT id_team, U.name, U.color, "solved" AS kind, time, coefficient, id_exercice, NULL, NULL FROM exercice_solved S NATURAL JOIN teams U WHERE id_exercice = ? UNION
SELECT id_team, U.name, U.color, "hint" AS kind, time, coefficient, id_exercice, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint NATURAL JOIN teams U WHERE id_exercice = ? UNION
SELECT id_team, U.name, U.color, "wchoices" AS kind, time, coefficient, id_exercice, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION

102
libfic/exercice_tries.go Normal file
View file

@ -0,0 +1,102 @@
package fic
import (
"time"
)
type ExerciceTryDetail struct {
Kind string `json:"kind"`
RelatedId int64 `json:"related"`
}
type ExerciceTry struct {
Id int64 `json:"id"`
IdTeam int64 `json:"id_team"`
Time time.Time `json:"time"`
Checksum []byte `json:"checksum"`
NbDiff int64 `json:"nb_diff"`
OneGood bool `json:"one_good"`
Details []ExerciceTryDetail `json:"details,omitempty"`
}
// TriesList returns a list of tries related to the exercice.
func (e *Exercice) TriesList() ([]*ExerciceTry, error) {
tries_table := "exercice_tries"
if SubmissionUniqueness {
tries_table = "exercice_distinct_tries"
}
if rows, err := DBQuery("SELECT id_try, id_team, time, cksum, nbdiff, onegood FROM "+tries_table+" WHERE id_exercice = ?", e.Id); err != nil {
return nil, err
} else {
defer rows.Close()
var tries []*ExerciceTry
for rows.Next() {
var try ExerciceTry
if err := rows.Scan(&try.Id, &try.IdTeam, &try.Time, &try.Checksum, &try.NbDiff, &try.OneGood); err != nil {
return nil, err
}
tries = append(tries, &try)
}
return tries, nil
}
}
func (e *Exercice) GetTry(id int64) (try *ExerciceTry, err error) {
try = &ExerciceTry{}
err = DBQueryRow("SELECT id_try, id_team, time, cksum, nbdiff, onegood FROM exercice_tries WHERE id_exercice = ? AND id_try = ?", e.Id, id).Scan(&try.Id, &try.IdTeam, &try.Time, &try.Checksum, &try.NbDiff, &try.OneGood)
return
}
func (try *ExerciceTry) FillDetails() error {
if rows, err := DBQuery("SELECT id_flag FROM exercice_tries_flags WHERE id_try = ?", try.Id); err != nil {
return err
} else {
defer rows.Close()
for rows.Next() {
var relatedid int64
if err := rows.Scan(&relatedid); err != nil {
return err
}
try.Details = append(try.Details, ExerciceTryDetail{
Kind: "flag",
RelatedId: relatedid,
})
}
}
if rows, err := DBQuery("SELECT id_mcq FROM exercice_tries_mcq WHERE id_try = ?", try.Id); err != nil {
return err
} else {
defer rows.Close()
for rows.Next() {
var relatedid int64
if err := rows.Scan(&relatedid); err != nil {
return err
}
try.Details = append(try.Details, ExerciceTryDetail{
Kind: "mcq",
RelatedId: relatedid,
})
}
}
return nil
}
func (try *ExerciceTry) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM exercice_tries WHERE id_try = ?", try.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}