From c5b65289d31ae09a4a3b7b4956d9db3c2e5abd36 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 2 Dec 2018 04:52:15 +0100 Subject: [PATCH] Add new helper string related to justified MCQ flag --- admin/sync/README.md | 5 +- admin/sync/exercice_defines.go | 1 + admin/sync/exercice_keys.go | 21 +++++-- backend/submission.go | 2 +- frontend/static/js/challenge.js | 22 +++---- frontend/static/views/defi.html | 2 +- libfic/flag.go | 2 +- libfic/mcq.go | 5 +- libfic/mcq_justification.go | 31 ++++++++++ libfic/team_my.go | 101 +++++++++++++++++--------------- 10 files changed, 122 insertions(+), 70 deletions(-) create mode 100644 libfic/mcq_justification.go diff --git a/admin/sync/README.md b/admin/sync/README.md index 5ba597b5..d56c3647 100644 --- a/admin/sync/README.md +++ b/admin/sync/README.md @@ -28,7 +28,8 @@ Tous les textes doivent utiliser l'encodage UTF8. * `label = "Intitulé du groupe"` : (facultatif) intitulé du groupe de choix ; * `[[flag_mcq.choice]]` : représente un choix, répétez autant de fois qu'il y a de choix : + `label = "Intitulé de la réponse"`, - + `value = true` : (facultatif, par défaut `false`) valeur attendue pour ce choix ; pour un QCM justifié, utilisez une chaîne de caractères (notez qu'il n'est pas possible de combiner des réponses vraies justifiées et justifiées) ; + + `value = true` : (facultatif, par défaut `false`) valeur attendue pour ce choix ; pour un QCM justifié, utilisez une chaîne de caractères (notez qu'il n'est pas possible de combiner des réponses vraies justifiées et justifiées), + + `help = "Flag correspondant"` : (facultatif) indication affichée dans le champ de texte des QCM justifiés ; - `[[flag_ucq]]` : drapeau sous forme de question à choix unique : * `label = "Intitulé du groupe"` : (facultatif) intitulé du groupe de choix ; * `raw = 'MieH2athxuPhai6u'` : drapeau attendu parmi les propositions ; @@ -222,5 +223,5 @@ Exemple `DIGESTS.txt` {#exemple-digeststxt} --- title: Format des répertoires pour la synchronisation author: FIC team 2019 -date: "Dernière mise à jour du document : 27 novembre 2018" +date: "Dernière mise à jour du document : 1 décembre 2018" --- diff --git a/admin/sync/exercice_defines.go b/admin/sync/exercice_defines.go index c1958bdb..d5237856 100644 --- a/admin/sync/exercice_defines.go +++ b/admin/sync/exercice_defines.go @@ -39,6 +39,7 @@ type ExerciceFlag struct { type ExerciceFlagMCQChoice struct { Label string Value interface{} `toml:",omitempty"` + Help string `toml:",omitempty"` } // ExerciceFlagMCQ holds information about a MCQ flag. diff --git a/admin/sync/exercice_keys.go b/admin/sync/exercice_keys.go index ab8e62f2..e586b080 100644 --- a/admin/sync/exercice_keys.go +++ b/admin/sync/exercice_keys.go @@ -123,33 +123,44 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) { isJustified := false for cid, choice := range quest.Choice { var val bool + var justify string + if choice.Value == nil { val = false } else if p, ok := choice.Value.(bool); ok { val = p + if len(choice.Help) > 0 { + errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: help field has to be used only with justified mcq.", path.Base(exercice.Path), nline + 1)) + continue + } if isJustified { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1)) continue } } else if p, ok := choice.Value.(string); ok { val = true + if len(choice.Help) == 0 { + choice.Help = "Flag correspondant" + } if hasOne && !isJustified { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1)) continue } isJustified = true - if _, err := exercice.AddRawFlag("%" + quest.Label + "%" + choice.Label, "", false, nil, []byte(p)); err != nil { - errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) - continue - } + justify = p } else { errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: has an invalid type. Expected true, false or a string", path.Base(exercice.Path), nline + 1, cid)) continue } - if _, err := flag.AddEntry(choice.Label, val); err != nil { + if e, err := flag.AddEntry(choice.Label, val); err != nil { errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err)) continue + } else if len(justify) > 0 { + if _, err := exercice.AddRawFlag(fmt.Sprintf("%%%d%%%s", e.Id, choice.Help), "", false, nil, []byte(justify)); err != nil { + errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) + continue + } } if val { diff --git a/backend/submission.go b/backend/submission.go index 052ed1cc..71120807 100644 --- a/backend/submission.go +++ b/backend/submission.go @@ -87,7 +87,7 @@ func treatSubmission(pathname string, team fic.Team, exercice_id string) { } else if mcq.IdExercice != exercice.Id { log.Println(id, "[ERR] We retrieve an invalid MCQ: from exercice", mcq.IdExercice, "whereas expected from exercice", exercice.Id) return - } else if key, err := mcq.GetJustifiedFlag(exercice, choice); err != nil { + } else if key, err := choice.GetJustifiedFlag(exercice); err != nil { // Most probably, we enter here because the selected choice has not to be justified // So, just ignore this case as it will be invalidated by the mcq validation continue diff --git a/frontend/static/js/challenge.js b/frontend/static/js/challenge.js index d7c1b7e6..8dbfe30e 100644 --- a/frontend/static/js/challenge.js +++ b/frontend/static/js/challenge.js @@ -233,18 +233,20 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) angular.forEach(data.exercices, function(exercice, eid) { angular.forEach(exercice.mcqs, function(mcq, mid) { angular.forEach(mcq.choices, function(choice, cid) { - this[cid] = { - label: choice, - value: mcq["checks_solved"] !== undefined && mcq["checks_solved"][cid] !== undefined && mcq["checks_solved"][cid] > 0 - }; - if (mcq["checks_solved"] !== undefined) { - this[cid].disabled = mcq["checks_solved"][cid] === undefined || mcq["checks_solved"][cid] >= 0; - this[cid].solved = mcq["checks_solved"][cid] !== undefined && mcq["checks_solved"][cid] >= 2; - } + if (!(choice instanceof Object)) + this[cid] = { + label: choice, + }; + + this[cid].disabled = mcq.solved || mcq.part_solved || this[cid].solved; + + if (!this[cid].help) + this[cid].help = "Flag correspondant"; + + if (!this[cid].disabled) + this[cid].value = $scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].mcqs[mid] && $scope.my.exercices[eid].mcqs[mid].choices[cid] && $scope.my.exercices[eid].mcqs[mid].choices[cid].value if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].mcqs[mid] && $scope.my.exercices[eid].mcqs[mid].choices[cid]) { - if ($scope.my.exercices[eid].mcqs[mid].choices[cid].value !== undefined) - this[cid].value = $scope.my.exercices[eid].mcqs[mid].choices[cid].value; if ($scope.my.exercices[eid].mcqs[mid].choices[cid].justify !== undefined) this[cid].justify = $scope.my.exercices[eid].mcqs[mid].choices[cid].justify; } diff --git a/frontend/static/views/defi.html b/frontend/static/views/defi.html index 05f03c27..d9cc5327 100644 --- a/frontend/static/views/defi.html +++ b/frontend/static/views/defi.html @@ -98,7 +98,7 @@
- +
diff --git a/libfic/flag.go b/libfic/flag.go index 10249ad6..0240385f 100644 --- a/libfic/flag.go +++ b/libfic/flag.go @@ -54,7 +54,7 @@ func (e Exercice) GetFlags() ([]Flag, error) { // GetFlagByLabel returns a flag matching the given label. func (e Exercice) GetFlagByLabel(label string) (k Flag, err error) { - err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum FROM exercice_flags WHERE type = ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum) + err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum) return } diff --git a/libfic/mcq.go b/libfic/mcq.go index 067858af..91c322dc 100644 --- a/libfic/mcq.go +++ b/libfic/mcq.go @@ -1,6 +1,7 @@ package fic import ( + "fmt" "time" ) @@ -175,8 +176,8 @@ func (e Exercice) WipeMCQs() (int64, error) { } // GetJustifiedFlag searchs for a flag in the scope of the given exercice. -func (m MCQ) GetJustifiedFlag(e Exercice, c MCQ_entry) (Flag, error) { - return e.GetFlagByLabel("%" + m.Title + "%" + c.Label) +func (c MCQ_entry) GetJustifiedFlag(e Exercice) (Flag, error) { + return e.GetFlagByLabel(fmt.Sprintf("\\%%%d\\%%%%", c.Id)) } // Check if the given vals are the expected ones to validate this flag. diff --git a/libfic/mcq_justification.go b/libfic/mcq_justification.go new file mode 100644 index 00000000..4d197398 --- /dev/null +++ b/libfic/mcq_justification.go @@ -0,0 +1,31 @@ +package fic + +import ( + "errors" + "strings" + "strconv" +) + +type FlagLabel struct { + Label string + IdChoice int64 + Checksum []byte + Solved bool +} + +// IsMCQJustification tells you if this key represent a justification from a MCQ. +func (k Flag) IsMCQJustification() (bool) { + return len(k.Label) > 0 && k.Label[0] == '%' +} + +// GetMCQJustification returns the structure corresponding to the given flag. +func (k Flag) GetMCQJustification() (fl FlagLabel, err error) { + spl := strings.Split(k.Label, "%") + if len(spl) >= 3 && len(spl[0]) == 0 { + fl.IdChoice, err = strconv.ParseInt(spl[1], 10, 64) + fl.Label = strings.Join(spl[2:], "%") + } else { + err = errors.New("This is not a MCQ justification") + } + return +} diff --git a/libfic/team_my.go b/libfic/team_my.go index 9aa64f29..f2842f3e 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -21,13 +21,19 @@ type myTeamHint struct { File string `json:"file,omitempty"` Cost int64 `json:"cost"` } +type myTeamMCQJustifiedChoice struct { + Label string `json:"label"` + Solved bool `json:"solved,omitempty"` + Value bool `json:"value,omitempty"` + Help string `json:"help,omitempty"` +} type myTeamMCQ struct { - Title string `json:"title"` - Justify bool `json:"justify,omitempty"` - Choices map[int64]string `json:"choices,omitempty"` - Solved *time.Time `json:"solved,omitempty"` - PSolved map[int64]int `json:"checks_solved,omitempty"` - Soluce string `json:"soluce,omitempty"` + Title string `json:"title"` + Justify bool `json:"justify,omitempty"` + Choices map[int64]interface{} `json:"choices,omitempty"` + Solved *time.Time `json:"solved,omitempty"` + PSolved *time.Time `json:"part_solved,omitempty"` + Soluce string `json:"soluce,omitempty"` } type myTeamFlag struct { Label string `json:"label"` @@ -156,8 +162,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { // Expose exercice flags - justifiedMCQ := map[string][]byte{} - justifiedMCQ_ids := map[string]int64{} + justifiedMCQ := map[int64]FlagLabel{} exercice.Flags = map[int64]myTeamFlag{} if flags, err := e.GetFlags(); err != nil { @@ -166,11 +171,12 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { for _, k := range flags { var flag myTeamFlag - if k.Label[0] == '%' { - justifiedMCQ[k.Label[1:]] = k.Checksum - if t == nil || t.HasPartiallySolved(k) == nil { - justifiedMCQ_ids[k.Label[1:]] = k.Id + if fl, err := k.GetMCQJustification(); err == nil { + fl.Checksum = k.Checksum + if t != nil && t.HasPartiallySolved(k) != nil { + fl.Solved = true } + justifiedMCQ[fl.IdChoice] = fl continue } else if t == nil { flag.Soluce = hex.EncodeToString(k.Checksum) @@ -206,65 +212,64 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { for _, mcq := range mcqs { m := myTeamMCQ{ Title: mcq.Title, - Choices: map[int64]string{}, + Choices: map[int64]interface{}{}, } soluce := "" - solved_justify := true + fullySolved := true + if t != nil { + m.PSolved = t.HasPartiallyRespond(mcq) + } for _, e := range mcq.Entries { - m.Choices[e.Id] = e.Label if e.Response { soluce += "t" } else { soluce += "f" } - if v, ok := justifiedMCQ[m.Title + "%" + e.Label]; ok { + + if v, ok := justifiedMCQ[e.Id]; ok { m.Justify = true if t == nil { - soluce += hex.EncodeToString(v) + soluce += hex.EncodeToString(v.Checksum) } - if _, ok := justifiedMCQ_ids[m.Title + "%" + e.Label]; ok { - solved_justify = false + if m.PSolved != nil || v.Solved { + jc := myTeamMCQJustifiedChoice{ + Label: e.Label, + Solved: v.Solved, + Value: v.Solved, + Help: v.Label, + } + + if !v.Solved { + fullySolved = false + } + + if PartialValidation && m.PSolved != nil { + jc.Value = e.Response + } + + m.Choices[e.Id] = jc + } else { + m.Choices[e.Id] = e.Label } + } else { + m.Choices[e.Id] = e.Label } } - if t != nil { - if solved_justify { - m.Solved = t.HasPartiallyRespond(mcq) - } else if PartialValidation && t.HasPartiallyRespond(mcq) != nil { - m.PSolved = map[int64]int{} - for _, e := range mcq.Entries { - if _, ok := justifiedMCQ[m.Title + "%" + e.Label]; ok { - if _, ok := justifiedMCQ_ids[m.Title + "%" + e.Label]; !ok { - m.PSolved[e.Id] = 2 - } else if e.Response { - m.PSolved[e.Id] = 1 - } - } - } - } else if PartialValidation { - m.PSolved = map[int64]int{} - for _, e := range mcq.Entries { - if _, ok := justifiedMCQ[m.Title + "%" + e.Label]; ok { - if _, ok := justifiedMCQ_ids[m.Title + "%" + e.Label]; !ok { - m.PSolved[e.Id] = 2 - } else { - m.PSolved[e.Id] = -1 - } - } else { - m.PSolved[e.Id] = -1 - } - } - } - } else { + if t == nil { h := getHashedFlag([]byte(soluce)) m.Soluce = hex.EncodeToString(h[:]) } + if fullySolved { + m.Solved = m.PSolved + m.PSolved = nil + } + exercice.MCQs[mcq.Id] = m } }