admin: add some stats about exercices
This commit is contained in:
parent
007efc6118
commit
5df1cc6e93
@ -23,6 +23,7 @@ func init() {
|
|||||||
router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
||||||
|
|
||||||
router.GET("/api/exercices/:eid/stats.json", apiHandler(exerciceHandler(getExerciceStats)))
|
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.GET("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(getExerciceHistory)))
|
||||||
router.PATCH("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(updateExerciceHistory)))
|
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 {
|
type exerciceStats struct {
|
||||||
|
IdExercice int64 `json:"id_exercice,omitempty"`
|
||||||
TeamTries int64 `json:"team_tries"`
|
TeamTries int64 `json:"team_tries"`
|
||||||
TotalTries int64 `json:"total_tries"`
|
TotalTries int64 `json:"total_tries"`
|
||||||
SolvedCount int64 `json:"solved_count"`
|
SolvedCount int64 `json:"solved_count"`
|
||||||
|
FlagSolved []int64 `json:"flag_solved"`
|
||||||
|
MCQSolved []int64 `json:"mcq_solved"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExerciceStats(e fic.Exercice, body []byte) (interface{}, error) {
|
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(),
|
TeamTries: e.TriedTeamCount(),
|
||||||
TotalTries: e.TriedCount(),
|
TotalTries: e.TriedCount(),
|
||||||
SolvedCount: e.SolvedCount(),
|
SolvedCount: e.SolvedCount(),
|
||||||
|
FlagSolved: e.FlagSolved(),
|
||||||
|
MCQSolved: e.MCQSolved(),
|
||||||
}, nil
|
}, 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 {
|
type uploadedExerciceHistory struct {
|
||||||
IdTeam int64 `json:"team_id"`
|
IdTeam int64 `json:"team_id"`
|
||||||
Kind string
|
Kind string
|
||||||
|
@ -25,6 +25,8 @@ func init() {
|
|||||||
router.GET("/api/themes/:thid/exercices", apiHandler(themeHandler(listThemedExercices)))
|
router.GET("/api/themes/:thid/exercices", apiHandler(themeHandler(listThemedExercices)))
|
||||||
router.POST("/api/themes/:thid/exercices", apiHandler(themeHandler(createExercice)))
|
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.GET("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
|
||||||
router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
||||||
router.DELETE("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
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) {
|
func deleteTheme(theme fic.Theme, _ []byte) (interface{}, error) {
|
||||||
return theme.Delete()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -247,6 +247,9 @@ angular.module("FICApp")
|
|||||||
.factory("ExerciceHistory", function($resource) {
|
.factory("ExerciceHistory", function($resource) {
|
||||||
return $resource("/api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
|
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) {
|
.factory("ExerciceStats", function($resource) {
|
||||||
return $resource("/api/exercices/:exerciceId/stats.json", { exerciceId: '@id' })
|
return $resource("/api/exercices/:exerciceId/stats.json", { exerciceId: '@id' })
|
||||||
})
|
})
|
||||||
@ -519,7 +522,6 @@ angular.module("FICApp")
|
|||||||
var refreshSyncReport = function() {
|
var refreshSyncReport = function() {
|
||||||
needRefreshSyncReportWhenReady = false;
|
needRefreshSyncReportWhenReady = false;
|
||||||
$http.get("full_import_report.json").then(function(response) {
|
$http.get("full_import_report.json").then(function(response) {
|
||||||
console.log(response.data);
|
|
||||||
$scope.syncReport = response.data;
|
$scope.syncReport = response.data;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@ -1404,12 +1406,12 @@ angular.module("FICApp")
|
|||||||
$scope.syncHints = true;
|
$scope.syncHints = true;
|
||||||
$scope.syncFlags = true;
|
$scope.syncFlags = true;
|
||||||
})
|
})
|
||||||
.controller("ExercicesListController", function($scope, ThemedExercice, $routeParams, $location, $rootScope, $http) {
|
.controller("ExercicesListController", function($scope, ThemedExercice, $location, $rootScope, $http) {
|
||||||
$scope.exercices = ThemedExercice.query({ themeId: $routeParams.themeId });
|
$scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id });
|
||||||
$scope.fields = ["title", "headline", "issue"];
|
$scope.fields = ["title", "headline", "issue"];
|
||||||
|
|
||||||
$scope.show = function(id) {
|
$scope.show = function(id) {
|
||||||
$location.url("/themes/" + $routeParams.themeId + "/exercices/" + id);
|
$location.url("/themes/" + $scope.theme.id + "/exercices/" + id);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.inSync = false;
|
$scope.inSync = false;
|
||||||
@ -1420,7 +1422,7 @@ angular.module("FICApp")
|
|||||||
method: "POST"
|
method: "POST"
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
$scope.inSync = false;
|
$scope.inSync = false;
|
||||||
$scope.exercices = ThemedExercice.query({ themeId: $routeParams.themeId });
|
$scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id });
|
||||||
$rootScope.staticFilesNeedUpdate++;
|
$rootScope.staticFilesNeedUpdate++;
|
||||||
if (response.data)
|
if (response.data)
|
||||||
$rootScope.newBox('warning', null, response.data, -1);
|
$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) {
|
.controller("ExerciceStatsController", function($scope, ExerciceStats, $routeParams) {
|
||||||
$scope.stats = ExerciceStats.get({ exerciceId: $routeParams.exerciceId });
|
$scope.stats = ExerciceStats.get({ exerciceId: $routeParams.exerciceId });
|
||||||
})
|
})
|
||||||
|
@ -64,6 +64,14 @@
|
|||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Défi validé par">Défi validé par</dt>
|
<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>
|
<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>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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).
|
// 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).
|
// 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) {
|
func (e Exercice) CheckResponse(cksum []byte, respflags map[int64]string, respmcq map[int64]bool, t Team) (bool, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user