admin: Better identify tries on exercice page
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
38e3a4efdf
commit
f6713c768b
6 changed files with 177 additions and 3 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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"] }}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
102
libfic/exercice_tries.go
Normal 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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue