admin: add some stats about exercices

This commit is contained in:
nemunaire 2020-01-29 15:57:34 +01:00
parent 007efc6118
commit 5df1cc6e93
5 changed files with 104 additions and 8 deletions

View File

@ -23,6 +23,7 @@ func init() {
router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
router.GET("/api/exercices/:eid/stats.json", apiHandler(exerciceHandler(getExerciceStats)))
router.GET("/api/exercices_stats.json", apiHandler(getExercicesStats))
router.GET("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(getExerciceHistory)))
router.PATCH("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(updateExerciceHistory)))
@ -170,9 +171,12 @@ func getExerciceHistory(exercice fic.Exercice, body []byte) (interface{}, error)
}
type exerciceStats struct {
TeamTries int64 `json:"team_tries"`
TotalTries int64 `json:"total_tries"`
SolvedCount int64 `json:"solved_count"`
IdExercice int64 `json:"id_exercice,omitempty"`
TeamTries int64 `json:"team_tries"`
TotalTries int64 `json:"total_tries"`
SolvedCount int64 `json:"solved_count"`
FlagSolved []int64 `json:"flag_solved"`
MCQSolved []int64 `json:"mcq_solved"`
}
func getExerciceStats(e fic.Exercice, body []byte) (interface{}, error) {
@ -180,9 +184,30 @@ func getExerciceStats(e fic.Exercice, body []byte) (interface{}, error) {
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
}, nil
}
func getExercicesStats(_ httprouter.Params, body []byte) (interface{}, error) {
if exercices, err := fic.GetExercices(); err != nil {
return nil, err
} else {
ret := []exerciceStats{}
for _, e := range exercices {
ret = append(ret, exerciceStats{
IdExercice: e.Id,
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
})
}
return ret, nil
}
}
type uploadedExerciceHistory struct {
IdTeam int64 `json:"team_id"`
Kind string

View File

@ -25,6 +25,8 @@ func init() {
router.GET("/api/themes/:thid/exercices", apiHandler(themeHandler(listThemedExercices)))
router.POST("/api/themes/:thid/exercices", apiHandler(themeHandler(createExercice)))
router.GET("/api/themes/:thid/exercices_stats.json", apiHandler(themeHandler(getThemedExercicesStats)))
router.GET("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
router.DELETE("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
@ -205,3 +207,22 @@ func updateTheme(theme fic.Theme, body []byte) (interface{}, error) {
func deleteTheme(theme fic.Theme, _ []byte) (interface{}, error) {
return theme.Delete()
}
func getThemedExercicesStats(theme fic.Theme, body []byte) (interface{}, error) {
if exercices, err := theme.GetExercices(); err != nil {
return nil, err
} else {
ret := []exerciceStats{}
for _, e := range exercices {
ret = append(ret, exerciceStats{
IdExercice: e.Id,
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
})
}
return ret, nil
}
}

View File

@ -247,6 +247,9 @@ angular.module("FICApp")
.factory("ExerciceHistory", function($resource) {
return $resource("/api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
})
.factory("ExercicesStats", function($resource) {
return $resource("/api/themes/:themeId/exercices_stats.json", { themeId: '@id' })
})
.factory("ExerciceStats", function($resource) {
return $resource("/api/exercices/:exerciceId/stats.json", { exerciceId: '@id' })
})
@ -519,7 +522,6 @@ angular.module("FICApp")
var refreshSyncReport = function() {
needRefreshSyncReportWhenReady = false;
$http.get("full_import_report.json").then(function(response) {
console.log(response.data);
$scope.syncReport = response.data;
})
};
@ -1404,12 +1406,12 @@ angular.module("FICApp")
$scope.syncHints = true;
$scope.syncFlags = true;
})
.controller("ExercicesListController", function($scope, ThemedExercice, $routeParams, $location, $rootScope, $http) {
$scope.exercices = ThemedExercice.query({ themeId: $routeParams.themeId });
.controller("ExercicesListController", function($scope, ThemedExercice, $location, $rootScope, $http) {
$scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id });
$scope.fields = ["title", "headline", "issue"];
$scope.show = function(id) {
$location.url("/themes/" + $routeParams.themeId + "/exercices/" + id);
$location.url("/themes/" + $scope.theme.id + "/exercices/" + id);
};
$scope.inSync = false;
@ -1420,7 +1422,7 @@ angular.module("FICApp")
method: "POST"
}).then(function(response) {
$scope.inSync = false;
$scope.exercices = ThemedExercice.query({ themeId: $routeParams.themeId });
$scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id });
$rootScope.staticFilesNeedUpdate++;
if (response.data)
$rootScope.newBox('warning', null, response.data, -1);
@ -1500,6 +1502,10 @@ angular.module("FICApp")
}
})
.controller("ExercicesStatsController", function($scope, ExercicesStats) {
$scope.exercices = ExercicesStats.query({ themeId: $scope.theme.id });
})
.controller("ExerciceStatsController", function($scope, ExerciceStats, $routeParams) {
$scope.stats = ExerciceStats.get({ exerciceId: $routeParams.exerciceId });
})

View File

@ -64,6 +64,14 @@
<dt class="col-sm-6 text-truncate" title="Défi validé par">Défi validé par</dt>
<dd class="col-sm-6"><ng-pluralize count="stats.solved_count" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize></dd>
<dt class="col-sm-6 text-truncate" title="Drapeaux validés">Drapeaux validés</dt>
<dd class="col-sm-6" title="{{ stats.flag_solved }}" ng-if="stats.flag_solved">{{ stats.flag_solved.length }}</dd>
<dd class="col-sm-6" title="{{ stats.flag_solved }}" ng-if="!stats.flag_solved">aucun</dd>
<dt class="col-sm-6 text-truncate" title="QCM validés">QCM validés</dt>
<dd class="col-sm-6" title="{{ stats.mcq_solved }}" ng-if="stats.mcq_solved">{{ stats.mcq_solved.length }}</dd>
<dd class="col-sm-6" title="{{ stats.mcq_solved }}" ng-if="!stats.mcq_solved">aucun</dd>
</dl>
</div>
</div>

View File

@ -360,6 +360,42 @@ func (e Exercice) TriedCount() int64 {
}
}
// FlagSolved returns the list of flags solved.
func (e Exercice) FlagSolved() (res []int64) {
if rows, err := DBQuery("SELECT F.id_flag FROM flag_found F INNER JOIN exercice_flags E ON E.id_flag = F.id_flag WHERE E.id_exercice = ? GROUP BY id_flag", e.Id); err != nil {
return
} else {
defer rows.Close()
for rows.Next() {
var n int64
if err := rows.Scan(&n); err != nil {
return
}
res = append(res, n)
}
return
}
}
// MCQSolved returns the list of mcqs solved.
func (e Exercice) MCQSolved() (res []int64) {
if rows, err := DBQuery("SELECT F.id_mcq FROM mcq_found F INNER JOIN exercice_mcq E ON E.id_mcq = F.id_mcq WHERE E.id_exercice = ? GROUP BY id_mcq", e.Id); err != nil {
return
} else {
defer rows.Close()
for rows.Next() {
var n int64
if err := rows.Scan(&n); err != nil {
return
}
res = append(res, n)
}
return
}
}
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
func (e Exercice) CheckResponse(cksum []byte, respflags map[int64]string, respmcq map[int64]bool, t Team) (bool, error) {