diff --git a/app/forms/main/create_bug_report.py b/app/forms/main/create_bug_report.py new file mode 100644 index 000000000..5e3541927 --- /dev/null +++ b/app/forms/main/create_bug_report.py @@ -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 "" diff --git a/app/scodoc/sco_bug_report.py b/app/scodoc/sco_bug_report.py new file mode 100644 index 000000000..3c4fdee0c --- /dev/null +++ b/app/scodoc/sco_bug_report.py @@ -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 diff --git a/app/scodoc/sco_dump_db.py b/app/scodoc/sco_dump_db.py index 1bef54391..b7a5f8aba 100644 --- a/app/scodoc/sco_dump_db.py +++ b/app/scodoc/sco_dump_db.py @@ -67,7 +67,7 @@ SCO_DUMP_LOCK = "/tmp/scodump.lock" def sco_dump_and_send_db( message: str = "", request_url: str = "", traceback_str_base64: str = "" -): +) -> requests.Response: """Dump base de données et l'envoie anonymisée pour debug""" traceback_str = base64.urlsafe_b64decode(traceback_str_base64).decode( scu.SCO_ENCODING @@ -97,7 +97,6 @@ def sco_dump_and_send_db( # Send r = _send_db(ano_db_name, message, request_url, traceback_str=traceback_str) - code = r.status_code finally: # Drop anonymized database @@ -107,7 +106,7 @@ def sco_dump_and_send_db( log("sco_dump_and_send_db: done.") - return code + return r def _duplicate_db(db_name, ano_db_name): diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 4178c0de3..f4130cb4a 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -719,6 +719,7 @@ SCO_DEV_MAIL = "emmanuel.viennet@gmail.com" # SVP ne pas changer # ne pas changer (ou vous perdez le support) SCO_DUMP_UP_URL = "https://scodoc.org/scodoc-installmgr/upload-dump" 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_EXT_TIMEOUT = 180 # appels à des ressources extérieures (siret, ...) SCO_TEST_API_TIMEOUT = 5 # pour tests unitaires API diff --git a/app/templates/sco_bug_report.j2 b/app/templates/sco_bug_report.j2 new file mode 100644 index 000000000..07bd189c9 --- /dev/null +++ b/app/templates/sco_bug_report.j2 @@ -0,0 +1,19 @@ +{# -*- mode: jinja-html -*- #} +{% extends 'base.j2' %} +{% import 'wtf.j2' as wtf %} + +{% block app_content %} +

Assistance technique

+

+ Ce formulaire permet d'effectuer une demande d'assistance technique.
+ Son contenu sera accessible publiquement sur scodoc.org, veuillez donc ne pas y inclure d'informations sensibles.
+ L'adresse email associée à votre compte ScoDoc est automatiquement transmise avec votre demande mais ne sera pas + affichée publiquement.
+

+ +
+
+ {{ wtf.quick_form(form) }} +
+
+{% endblock app_content %} diff --git a/app/templates/scolar/index.j2 b/app/templates/scolar/index.j2 index 74cd8a42b..21d67fe8c 100644 --- a/app/templates/scolar/index.j2 +++ b/app/templates/scolar/index.j2 @@ -297,7 +297,7 @@ div.effectif { rel="noopener noreferrer">Contact (Discord)
  • - Envoyer données + Créer un ticket
  • diff --git a/app/views/scolar.py b/app/views/scolar.py index de147fee3..b9953863d 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -85,6 +85,7 @@ from app.scodoc import ( html_sco_header, sco_import_etuds, sco_archives_etud, + sco_bug_report, sco_cache, sco_debouche, sco_dept, @@ -109,6 +110,7 @@ from app.scodoc import ( sco_up_to_date, ) from app.tables import list_etuds +from app.forms.main.create_bug_report import CreateBugReport 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=""): "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 ) + + status_code = r.status_code + + try: + r_msg = r.json()["message"] + except (requests.exceptions.JSONDecodeError, KeyError): + r_msg = "Erreur: code " + +status_code + +' Merci de contacter ' + +scu.SCO_DEV_MAIL + +"" + H = [html_sco_header.sco_header(page_title="Assistance technique")] - if status_code == requests.codes.INSUFFICIENT_STORAGE: # pylint: disable=no-member - H.append( - """

    - Erreur: espace serveur trop plein. - Merci de contacter {0}

    """.format( - scu.SCO_DEV_MAIL - ) - ) - elif status_code == requests.codes.OK: # pylint: disable=no-member - H.append("""

    Opération effectuée.

    """) + if status_code == requests.codes.OK: # pylint: disable=no-member + H.append(f"""

    Opération effectuée.

    {r_msg}

    """) else: - H.append( - f"""

    - Erreur: code {status_code} - Merci de contacter {scu.SCO_DEV_MAIL}

    """ - ) + H.append(f"""

    {r_msg}

    """) flash("Données envoyées au serveur d'assistance") 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 " + + str(status_code) + + ' Merci de contacter ' + + scu.SCO_DEV_MAIL + + "" + ) + + H = [html_sco_header.sco_header(page_title="Assistance technique")] + if r.status_code >= 200 and r.status_code < 300: + H.append(f"""

    Opération effectuée.

    {r_msg}

    """) + else: + H.append(f"""

    {r_msg}

    """) + return "\n".join(H) + html_sco_header.sco_footer() + + return render_template( + "sco_bug_report.j2", + form=form, + )