Merge branch 'report' of https://scodoc.org/git/lyanis/ScoDoc into lyanis-report

This commit is contained in:
Emmanuel Viennet 2024-05-26 20:14:58 +02:00
commit 9a882ea41d
7 changed files with 246 additions and 20 deletions

View File

@ -0,0 +1,64 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""
Formulaire création de ticket de bug
"""
from flask_wtf import FlaskForm
from wtforms import SubmitField, validators
from wtforms.fields.simple import StringField, TextAreaField, BooleanField
from app.scodoc import sco_preferences
class CreateBugReport(FlaskForm):
"""Formulaire permettant la création d'un ticket de bug"""
title = StringField(
label="Titre du ticket",
validators=[
validators.DataRequired("titre du ticket requis"),
],
)
message = TextAreaField(
label="Message",
id="ticket_message",
validators=[
validators.DataRequired("message du ticket requis"),
],
)
etab = StringField(label="Etablissement")
include_dump = BooleanField(
"Inclure une copie anonymisée de la base de données ? (ces données permettent aux développeur·euse·s de reproduire l'erreur rencontrée)",
default=False,
)
submit = SubmitField("Envoyer")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
def __init__(self, *args, **kwargs):
super(CreateBugReport, self).__init__(*args, **kwargs)
self.etab.data = sco_preferences.get_preference("InstituteName") or ""

View File

@ -0,0 +1,97 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Rapport de bug ScoDoc
Permet de créer un rapport de bug (ticket) sur la plateforme git scodoc.org.
Le principe est le suivant:
1- Si l'utilisateur le demande, on dump la base de données et on l'envoie
2- ScoDoc envoie une requête POST à scodoc.org pour qu'un ticket git soit créé avec les
informations fournies par l'utilisateur + quelques métadonnées.
"""
import app.scodoc.sco_utils as scu, sco_version, requests
from flask import g
from flask_login import current_user
from app import log
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_dump_db import sco_dump_and_send_db
def sco_bug_report(
title: str = "", message: str = "", etab: str = "", include_dump: bool = False
) -> requests.Response:
dump_id = None
if include_dump:
dump = sco_dump_and_send_db()
try:
dump_id = dump.json()["dump_id"]
except (requests.exceptions.JSONDecodeError, KeyError):
dump_id = "inconnu"
try:
r = requests.post(
scu.SCO_BUG_REPORT_URL,
json={
"ticket": {
"title": title,
"message": message,
"etab": etab,
"dept": getattr(g, "scodoc_dept", "-"),
},
"user": {
"name": current_user.get_nomcomplet(),
"email": current_user.email,
},
"dump": {
"included": include_dump,
"id": dump_id,
},
"scodoc": {
"version": sco_version.SCOVERSION,
},
},
timeout=scu.SCO_ORG_TIMEOUT,
)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as exc:
log("ConnectionError: Impossible de joindre le serveur d'assistance")
raise ScoValueError(
"""
Impossible de joindre le serveur d'assistance (scodoc.org).
Veuillez contacter le service informatique de votre établissement pour
corriger la configuration de ScoDoc. Dans la plupart des cas, il
s'agit d'un proxy mal configuré.
"""
) from exc
return r

View File

@ -67,7 +67,7 @@ SCO_DUMP_LOCK = "/tmp/scodump.lock"
def sco_dump_and_send_db( def sco_dump_and_send_db(
message: str = "", request_url: str = "", traceback_str_base64: str = "" message: str = "", request_url: str = "", traceback_str_base64: str = ""
): ) -> requests.Response:
"""Dump base de données et l'envoie anonymisée pour debug""" """Dump base de données et l'envoie anonymisée pour debug"""
traceback_str = base64.urlsafe_b64decode(traceback_str_base64).decode( traceback_str = base64.urlsafe_b64decode(traceback_str_base64).decode(
scu.SCO_ENCODING scu.SCO_ENCODING
@ -97,7 +97,6 @@ def sco_dump_and_send_db(
# Send # Send
r = _send_db(ano_db_name, message, request_url, traceback_str=traceback_str) r = _send_db(ano_db_name, message, request_url, traceback_str=traceback_str)
code = r.status_code
finally: finally:
# Drop anonymized database # Drop anonymized database
@ -107,7 +106,7 @@ def sco_dump_and_send_db(
log("sco_dump_and_send_db: done.") log("sco_dump_and_send_db: done.")
return code return r
def _duplicate_db(db_name, ano_db_name): def _duplicate_db(db_name, ano_db_name):

View File

@ -719,6 +719,7 @@ SCO_DEV_MAIL = "emmanuel.viennet@gmail.com" # SVP ne pas changer
# ne pas changer (ou vous perdez le support) # ne pas changer (ou vous perdez le support)
SCO_DUMP_UP_URL = "https://scodoc.org/scodoc-installmgr/upload-dump" SCO_DUMP_UP_URL = "https://scodoc.org/scodoc-installmgr/upload-dump"
SCO_UP2DATE = "https://scodoc.org/scodoc-installmgr/check_version" SCO_UP2DATE = "https://scodoc.org/scodoc-installmgr/check_version"
SCO_BUG_REPORT_URL = "https://scodoc.org/scodoc-installmgr/report"
SCO_ORG_TIMEOUT = 180 # contacts scodoc.org SCO_ORG_TIMEOUT = 180 # contacts scodoc.org
SCO_EXT_TIMEOUT = 180 # appels à des ressources extérieures (siret, ...) SCO_EXT_TIMEOUT = 180 # appels à des ressources extérieures (siret, ...)
SCO_TEST_API_TIMEOUT = 5 # pour tests unitaires API SCO_TEST_API_TIMEOUT = 5 # pour tests unitaires API

View File

@ -0,0 +1,19 @@
{# -*- mode: jinja-html -*- #}
{% extends 'base.j2' %}
{% import 'wtf.j2' as wtf %}
{% block app_content %}
<h2>Assistance technique</h2>
<p class="help">
Ce formulaire permet d'effectuer une demande d'assistance technique.<br>
Son <b>contenu sera accessible publiquement</b> sur scodoc.org, veuillez donc ne pas y inclure d'informations sensibles.<br>
L'adresse email associée à votre compte ScoDoc est automatiquement transmise avec votre demande mais ne sera pas
affichée publiquement.<br>
</p>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock app_content %}

View File

@ -297,7 +297,7 @@ div.effectif {
rel="noopener noreferrer">Contact (Discord)</a> rel="noopener noreferrer">Contact (Discord)</a>
</li> </li>
<li> <li>
<a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a> <a class="stdlink" href="sco_bug_report">Créer un ticket</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -85,6 +85,7 @@ from app.scodoc import (
html_sco_header, html_sco_header,
sco_import_etuds, sco_import_etuds,
sco_archives_etud, sco_archives_etud,
sco_bug_report,
sco_cache, sco_cache,
sco_debouche, sco_debouche,
sco_dept, sco_dept,
@ -109,6 +110,7 @@ from app.scodoc import (
sco_up_to_date, sco_up_to_date,
) )
from app.tables import list_etuds from app.tables import list_etuds
from app.forms.main.create_bug_report import CreateBugReport
def sco_publish(route, function, permission, methods=["GET"]): def sco_publish(route, function, permission, methods=["GET"]):
@ -2534,25 +2536,69 @@ def stat_bac(formsemestre_id):
def sco_dump_and_send_db(message="", request_url="", traceback_str_base64=""): def sco_dump_and_send_db(message="", request_url="", traceback_str_base64=""):
"Send anonymized data to supervision" "Send anonymized data to supervision"
status_code = sco_dump_db.sco_dump_and_send_db( r = sco_dump_db.sco_dump_and_send_db(
message, request_url, traceback_str_base64=traceback_str_base64 message, request_url, traceback_str_base64=traceback_str_base64
) )
status_code = r.status_code
try:
r_msg = r.json()["message"]
except (requests.exceptions.JSONDecodeError, KeyError):
r_msg = "Erreur: code <tt>"
+status_code
+'</tt> Merci de contacter <a href="mailto:'
+scu.SCO_DEV_MAIL
+'">'
+scu.SCO_DEV_MAIL
+"</a>"
H = [html_sco_header.sco_header(page_title="Assistance technique")] H = [html_sco_header.sco_header(page_title="Assistance technique")]
if status_code == requests.codes.INSUFFICIENT_STORAGE: # pylint: disable=no-member if status_code == requests.codes.OK: # pylint: disable=no-member
H.append( H.append(f"""<p>Opération effectuée.</p><p>{r_msg}</p>""")
"""<p class="warning">
Erreur: espace serveur trop plein.
Merci de contacter <a href="mailto:{0}">{0}</a></p>""".format(
scu.SCO_DEV_MAIL
)
)
elif status_code == requests.codes.OK: # pylint: disable=no-member
H.append("""<p>Opération effectuée.</p>""")
else: else:
H.append( H.append(f"""<p class="warning">{r_msg}</p>""")
f"""<p class="warning">
Erreur: code <tt>{status_code}</tt>
Merci de contacter <a href="mailto:{scu.SCO_DEV_MAIL}">{scu.SCO_DEV_MAIL}</a></p>"""
)
flash("Données envoyées au serveur d'assistance") flash("Données envoyées au serveur d'assistance")
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()
# --- Report form (assistance)
@bp.route("/sco_bug_report", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView)
def sco_bug_report_form():
"Formulaire de création d'un ticket d'assistance"
form = CreateBugReport()
if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index"))
if form.validate_on_submit():
r = sco_bug_report.sco_bug_report(
form.title.data, form.message.data, form.etab.data, form.include_dump.data
)
status_code = r.status_code
try:
r_msg = r.json()["message"]
except (requests.exceptions.JSONDecodeError, KeyError):
r_msg = (
"Erreur: code <tt>"
+ str(status_code)
+ '</tt> Merci de contacter <a href="mailto:'
+ scu.SCO_DEV_MAIL
+ '">'
+ scu.SCO_DEV_MAIL
+ "</a>"
)
H = [html_sco_header.sco_header(page_title="Assistance technique")]
if r.status_code >= 200 and r.status_code < 300:
H.append(f"""<p>Opération effectuée.</p><p>{r_msg}</p>""")
else:
H.append(f"""<p class="warning">{r_msg}</p>""")
return "\n".join(H) + html_sco_header.sco_footer()
return render_template(
"sco_bug_report.j2",
form=form,
)