From d77e6e2eacabde2f142ddcc83d4c50ca968d4029 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 28 Feb 2022 10:52:27 +0100 Subject: [PATCH] Able to make corrections --- atsebayt/src/components/Correction.svelte | 47 ++++++ .../src/components/CorrectionReference.svelte | 112 +++++++++++++ .../CorrectionResponseFooter.svelte | 124 ++++++++++++++ .../src/components/CorrectionResponses.svelte | 130 +++++++++++++++ atsebayt/src/components/QuestionForm.svelte | 50 +++--- atsebayt/src/components/QuestionHeader.svelte | 44 +++++ .../src/components/QuestionProposals.svelte | 16 +- .../src/components/ResponseCorrected.svelte | 52 ++++++ atsebayt/src/components/SurveyList.svelte | 2 +- .../src/components/SurveyQuestions.svelte | 15 +- atsebayt/src/lib/correctionTemplates.js | 88 ++++++++++ atsebayt/src/lib/questions.js | 23 +++ atsebayt/src/lib/response.js | 34 ++++ atsebayt/src/lib/surveys.js | 1 - atsebayt/src/routes/__layout.svelte | 4 + .../src/routes/surveys/[sid]/__layout.svelte | 39 +++++ .../{[sid].svelte => [sid]/index.svelte} | 37 ++--- .../surveys/[sid]/responses/[rid].svelte | 156 ++++++++++++++++++ .../surveys/[sid]/responses/index.svelte | 85 ++++++++++ corrections.go | 89 +++++++++- db.go | 1 + responses.go | 16 ++ 22 files changed, 1085 insertions(+), 80 deletions(-) create mode 100644 atsebayt/src/components/Correction.svelte create mode 100644 atsebayt/src/components/CorrectionReference.svelte create mode 100644 atsebayt/src/components/CorrectionResponseFooter.svelte create mode 100644 atsebayt/src/components/CorrectionResponses.svelte create mode 100644 atsebayt/src/components/QuestionHeader.svelte create mode 100644 atsebayt/src/components/ResponseCorrected.svelte create mode 100644 atsebayt/src/lib/correctionTemplates.js create mode 100644 atsebayt/src/lib/response.js create mode 100644 atsebayt/src/routes/surveys/[sid]/__layout.svelte rename atsebayt/src/routes/surveys/{[sid].svelte => [sid]/index.svelte} (56%) create mode 100644 atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte create mode 100644 atsebayt/src/routes/surveys/[sid]/responses/index.svelte diff --git a/atsebayt/src/components/Correction.svelte b/atsebayt/src/components/Correction.svelte new file mode 100644 index 0000000..3caae1a --- /dev/null +++ b/atsebayt/src/components/Correction.svelte @@ -0,0 +1,47 @@ + + +{#if question && (question.kind == 'mcq' || question.kind == 'ucq')} + {#await question.getProposals()} +
+
+ Récupération des propositions… +
+ {:then proposals} + dispatch('nb_responses', v.detail)} + /> + {/await} +{:else} + dispatch('nb_responses', v.detail)} + /> +{/if} diff --git a/atsebayt/src/components/CorrectionReference.svelte b/atsebayt/src/components/CorrectionReference.svelte new file mode 100644 index 0000000..83b62fd --- /dev/null +++ b/atsebayt/src/components/CorrectionReference.svelte @@ -0,0 +1,112 @@ + + +
+ {#each templates as template (template.id)} +
submitTemplate(template)}> +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ {#if cts && template.id && cts[template.id.toString()]} + {Math.trunc(Object.keys(cts[template.id.toString()]).length/nb_responses*1000)/10} % + {:else} + N/A + {/if} +
+
+ + +
+
+
+ {/each} + +
diff --git a/atsebayt/src/components/CorrectionResponseFooter.svelte b/atsebayt/src/components/CorrectionResponseFooter.svelte new file mode 100644 index 0000000..bcf3c8a --- /dev/null +++ b/atsebayt/src/components/CorrectionResponseFooter.svelte @@ -0,0 +1,124 @@ + + +
+
+ +
+
+
+ {#each templates as template (template.id)} +
+ {my_tpls[template.id] = !my_tpls[template.id]; autoCorrection(response.id_user, my_tpls).then((r) => my_correction = r); }} + checked={my_tpls[template.id]} + > + +
+ {/each} +
+
+
+ +
+ + {#if my_correction} + + {/if} + /100 +
+ + +
+
+{#if my_correction} +
100} + class:alert-success={my_correction.score >= 95 && my_correction.score <= 100} + class:alert-info={my_correction.score < 95 && my_correction.score >= 70} + class:alert-warning={my_correction.score < 70 && my_correction.score >= 45} + class:alert-danger={my_correction.score < 45} + > + + {my_correction.score} % + +
+ {my_correction.score_explaination} +
+
+{/if} diff --git a/atsebayt/src/components/CorrectionResponses.svelte b/atsebayt/src/components/CorrectionResponses.svelte new file mode 100644 index 0000000..e36b584 --- /dev/null +++ b/atsebayt/src/components/CorrectionResponses.svelte @@ -0,0 +1,130 @@ + + +{#await req_responses} +
+
+ Récupération des réponses… +
+{:then} + {#each filteredResponses as response, rid (response.id)} +
+
+
+
+ {#if question.kind == 'mcq' || question.kind == 'ucq'} + {#if !proposals} +
+ Une erreur s'est produite, aucune proposition n'a été chargée +
+ {:else} + + {/if} + {:else} +

+ {response.value} +

+ {/if} + +
+ +
+
+ {#if showStudent} +
+
+ {#await getUser(response.id_user)} +
+ {:then user} + + avatar {user.login} +
+ {user.login} +
+
+ {/await} +
+
+ {/if} +
+ {/each} +{/await} diff --git a/atsebayt/src/components/QuestionForm.svelte b/atsebayt/src/components/QuestionForm.svelte index 841292d..71b9449 100644 --- a/atsebayt/src/components/QuestionForm.svelte +++ b/atsebayt/src/components/QuestionForm.svelte @@ -1,16 +1,20 @@
-
- {#if $user.is_admin} + + {#if $user && $user.is_admin} @@ -43,34 +51,7 @@ {/if} {/if} - - {#if edit} -
- -
-
- {:else} -

{qid + 1}. {question.title}

- {/if} - - {#if edit} -
- -
- -
-
- - - {:else if question.description} -

{@html question.description}

- {/if} -
+
{#if false && response_history}
@@ -132,6 +113,13 @@ {/if} + {#if survey.corrected} + + {/if} + {#if false}
diff --git a/atsebayt/src/components/QuestionHeader.svelte b/atsebayt/src/components/QuestionHeader.svelte new file mode 100644 index 0000000..32ea803 --- /dev/null +++ b/atsebayt/src/components/QuestionHeader.svelte @@ -0,0 +1,44 @@ + + +
+ + + {#if edit} +
+ +
+
+ {:else} +

{#if qid !== null}{qid + 1}. {/if}{question.title}

+ {/if} + + {#if edit} +
+ +
+ +
+
+ + + {:else if question.description} +

{@html question.description}

+ {/if} +
diff --git a/atsebayt/src/components/QuestionProposals.svelte b/atsebayt/src/components/QuestionProposals.svelte index ab990f1..f3c0fb2 100644 --- a/atsebayt/src/components/QuestionProposals.svelte +++ b/atsebayt/src/components/QuestionProposals.svelte @@ -4,13 +4,13 @@ export let edit = false; export let proposals = []; export let kind = 'mcq'; + export let prefixid = ''; export let readonly = false; export let id_question = 0; export let value; let valueCheck = []; $: { - console.log(value); if (value) { valueCheck = value.split(','); } @@ -31,10 +31,10 @@ type="checkbox" class="form-check-input" disabled={readonly} - name={'proposal' + proposal.id_question} - id={'p' + proposal.id} + name={prefixid + 'proposal' + proposal.id_question} + id={prefixid + 'p' + proposal.id} bind:group={valueCheck} - value={String(proposal.id)} + value={proposal.id.toString()} on:change={() => { value = valueCheck.join(',')}} > {:else} @@ -42,10 +42,10 @@ type="radio" class="form-check-input" disabled={readonly} - name={'proposal' + proposal.id_question} - id={'p' + proposal.id} + name={prefixid + 'proposal' + proposal.id_question} + id={prefixid + 'p' + proposal.id} bind:group={value} - value={String(proposal.id)} + value={proposal.id.toString()} > {/if} {#if edit} @@ -80,7 +80,7 @@ {:else} diff --git a/atsebayt/src/components/ResponseCorrected.svelte b/atsebayt/src/components/ResponseCorrected.svelte new file mode 100644 index 0000000..54f3eea --- /dev/null +++ b/atsebayt/src/components/ResponseCorrected.svelte @@ -0,0 +1,52 @@ + + +{#if response.score !== undefined} +
= 95} + class:alert-info={response.score < 95 && response.score >= 70} + class:alert-warning={response.score < 70 && response.score >= 45} + class:alert-danger={response.score < 45} + > +
+ + {response.score} % + +
+
+ {#if response.score_explaination} + {response.score_explaination} + {:else if response.score === 100} + + {/if} +
+
+{:else if response && survey} + {#if response.value} +
+
+ 🤯 +
+
+ Oups, tu sembles être passé entre les mailles du filet ! + Cette question a bien été corrigée, mais une erreur s'est produite dans la correction de ta réponse. + Contacte ton enseignant au plus vite. +
+
+ {:else} +
+
+ 😟 +
+
+ Tu n'as pas répondu à cette question. + Que s'est-il passé ? +
+
+ {/if} +{/if} diff --git a/atsebayt/src/components/SurveyList.svelte b/atsebayt/src/components/SurveyList.svelte index 0c2e2b8..807f079 100644 --- a/atsebayt/src/components/SurveyList.svelte +++ b/atsebayt/src/components/SurveyList.svelte @@ -36,7 +36,7 @@ {/if} - goto(`surveys/${survey.id}`)}> + goto($user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> {survey.title} diff --git a/atsebayt/src/components/SurveyQuestions.svelte b/atsebayt/src/components/SurveyQuestions.svelte index 3f5de62..a906418 100644 --- a/atsebayt/src/components/SurveyQuestions.svelte +++ b/atsebayt/src/components/SurveyQuestions.svelte @@ -68,6 +68,7 @@
{#each questions as question, qid (question.id)} - + {#if !survey.corrected || $user.is_admin} + + {/if} {#if $user && $user.is_admin} @@ -51,12 +48,4 @@ {:then questions} {/await} -{:catch error} -
-

- < - Questionnaire introuvable -

- {error} -
{/await} diff --git a/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte b/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte new file mode 100644 index 0000000..1303d6b --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte @@ -0,0 +1,156 @@ + + + + +{#await surveyP then survey} + {#await getQuestion(rid)} +
+
+ Chargement de la question… +
+ {:then question} + {#await ctpls} +
+
+ Chargement de la question… +
+ {:then correctionTemplates} +
+ +
+
+

+ < + {survey.title} + Corrections +

+ +
+ +
+ + + {#if showResponses} + + {/if} + + + + {#if showResponses} + + {/if} +
+ + { nb_responses = v.detail; } } + /> + {/await} + {/await} +{/await} + +
diff --git a/atsebayt/src/routes/surveys/[sid]/responses/index.svelte b/atsebayt/src/routes/surveys/[sid]/responses/index.svelte new file mode 100644 index 0000000..0c344ba --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/responses/index.svelte @@ -0,0 +1,85 @@ + + + + +{#await surveyP then survey} +
+

+ < + {survey.title} + Corrections +

+ +
+ + {#await getQuestions(survey.id)} +
+
+ Chargement des questions … +
+ {:then questions} +
+ + + + + + + + + + {#each questions as question (question.id)} + + + {#await question.getResponses()} + + {:then responses} + + + {/await} + + {/each} + + + + + + + +
QuestionRéponsesMoyenne
{question.title} +
+ Chargement … +
+ {#if responses} + {responses.filter((r) => !r.time_scored).length} / + {responses.length} + {:else} + 0 + {/if} + + {#if responses && responses.filter((r) => r.time_scored).length} + {responses.reduce((p, c) => (p + c.score?c.score:0), 0)/responses.filter((r) => r.time_scored).length} + {:else} + -- % + {/if} +
Moyenne %
+
+ {/await} +{/await} diff --git a/corrections.go b/corrections.go index bed8721..8402e37 100644 --- a/corrections.go +++ b/corrections.go @@ -23,7 +23,7 @@ func init() { return APIErrorResponse{err: err} } - return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.Score, new.ScoreExplaination)) + return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination)) }), adminRestricted)) router.GET("/api/surveys/:sid/questions/:qid/corrections/:cid", apiHandler(correctionHandler( @@ -51,6 +51,48 @@ func init() { return formatApiResponse(ct.Delete()) }), adminRestricted)) + router.GET("/api/questions/:qid/corrections", apiHandler(questionHandler( + func(q Question, _ []byte) HTTPResponse { + if cts, err := q.GetCorrectionTemplates(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{cts} + } + }), adminRestricted)) + router.POST("/api/questions/:qid/corrections", apiHandler(questionHandler(func(q Question, body []byte) HTTPResponse { + var new CorrectionTemplate + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination)) + }), adminRestricted)) + + router.GET("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler( + func(ct CorrectionTemplate, _ []byte) HTTPResponse { + if users, err := ct.GetUserCorrected(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{users} + } + }), adminRestricted)) + router.PUT("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(current CorrectionTemplate, body []byte) HTTPResponse { + var new CorrectionTemplate + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + new.Id = current.Id + if err := new.Update(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{new} + } + }), adminRestricted)) + router.DELETE("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(ct CorrectionTemplate, body []byte) HTTPResponse { + return formatApiResponse(ct.Delete()) + }), adminRestricted)) + router.GET("/api/users/:uid/questions/:qid", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse { return userHandler(func(u User, _ []byte) HTTPResponse { if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err != nil { @@ -75,6 +117,14 @@ func init() { return formatApiResponse(u.NewCorrection(new.IdTemplate)) }), adminRestricted)) + router.PUT("/api/users/:uid/corrections", apiHandler(userHandler(func(u User, body []byte) HTTPResponse { + var new map[int64]bool + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + return formatApiResponse(u.EraseCorrections(new)) + }), adminRestricted)) router.DELETE("/api/users/:uid/corrections/:cid", apiHandler(userCorrectionHandler(func(u User, uc UserCorrection, body []byte) HTTPResponse { return formatApiResponse(uc.Delete(u)) }), adminRestricted)) @@ -112,19 +162,20 @@ type CorrectionTemplate struct { Id int64 `json:"id"` IdQuestion int64 `json:"id_question"` Label string `json:"label"` + RegExp string `json:"regexp"` Score int `json:"score"` ScoreExplaination string `json:"score_explaination,omitempty"` } func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error) { - if rows, errr := DBQuery("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_question=?", q.Id); errr != nil { + if rows, errr := DBQuery("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=?", q.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var c CorrectionTemplate - if err = rows.Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination); err != nil { + if err = rows.Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination); err != nil { return } ct = append(ct, c) @@ -138,27 +189,27 @@ func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error) } func (q *Question) GetCorrectionTemplate(id int) (c CorrectionTemplate, err error) { - err = DBQueryRow("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_question=? AND id_template=?", q.Id, id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination) + err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=? AND id_template=?", q.Id, id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination) return } func GetCorrectionTemplate(id int64) (c CorrectionTemplate, err error) { - err = DBQueryRow("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_template=?", id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination) + err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_template=?", id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination) return } -func (q *Question) NewCorrectionTemplate(label string, score int, score_explaination string) (CorrectionTemplate, error) { - if res, err := DBExec("INSERT INTO correction_templates (id_question, label, score, score_explanation) VALUES (?, ?, ?, ?)", q.Id, label, score, score_explaination); err != nil { +func (q *Question) NewCorrectionTemplate(label string, regexp string, score int, score_explaination string) (CorrectionTemplate, error) { + if res, err := DBExec("INSERT INTO correction_templates (id_question, label, re, score, score_explanation) VALUES (?, ?, ?, ?, ?)", q.Id, label, regexp, score, score_explaination); err != nil { return CorrectionTemplate{}, err } else if cid, err := res.LastInsertId(); err != nil { return CorrectionTemplate{}, err } else { - return CorrectionTemplate{cid, q.Id, label, score, score_explaination}, nil + return CorrectionTemplate{cid, q.Id, label, regexp, score, score_explaination}, nil } } func (t *CorrectionTemplate) Update() error { - _, err := DBExec("UPDATE correction_templates SET id_question = ?, label = ?, score = ?, score_explanation = ? WHERE id_template = ?", t.IdQuestion, t.Label, t.Score, t.ScoreExplaination, t.Id) + _, err := DBExec("UPDATE correction_templates SET id_question = ?, label = ?, re = ?, score = ?, score_explanation = ? WHERE id_template = ?", t.IdQuestion, t.Label, t.RegExp, t.Score, t.ScoreExplaination, t.Id) return err } @@ -269,6 +320,26 @@ func (u *User) NewCorrection(id int64) (*UserCorrectionSummary, error) { } } +func (u *User) EraseCorrections(ids map[int64]bool) (*UserCorrectionSummary, error) { + var lastid int64 + + for id, st := range ids { + lastid = id + if st { + DBExec("INSERT INTO student_corrected (id_user, id_template) VALUES (?, ?)", u.Id, id) + } else { + DBExec("DELETE FROM student_corrected WHERE id_user = ? AND id_template = ?", u.Id, id) + } + + } + + if ucs, err := u.ComputeScoreQuestion(lastid); err != nil { + return nil, err + } else { + return ucs, nil + } +} + func (c *UserCorrection) Delete(u User) (*UserCorrectionSummary, error) { if res, err := DBExec("DELETE FROM student_corrected WHERE id_correction = ?", c.Id); err != nil { return nil, err diff --git a/db.go b/db.go index de129e2..d6a90a3 100644 --- a/db.go +++ b/db.go @@ -137,6 +137,7 @@ CREATE TABLE IF NOT EXISTS correction_templates( id_template INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_question INTEGER NOT NULL, label VARCHAR(255) NOT NULL, + re VARCHAR(255) NOT NULL, score INTEGER, score_explanation TEXT, FOREIGN KEY(id_question) REFERENCES survey_quests(id_question) diff --git a/responses.go b/responses.go index e41a62d..d89f13a 100644 --- a/responses.go +++ b/responses.go @@ -85,6 +85,22 @@ func init() { new.TimeScored = &now } + new.Id = current.Id + new.IdUser = current.IdUser + return formatApiResponse(new.Update()) + }), adminRestricted)) + router.PUT("/api/questions/:qid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse { + var new Response + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + if new.Score != nil && (current.Score == nil || *new.Score != *current.Score) { + now := time.Now() + new.IdCorrector = &u.Id + new.TimeScored = &now + } + new.Id = current.Id new.IdUser = current.IdUser return formatApiResponse(new.Update())