From fab2b745ac857c75109e059a7edd2b9c29fe3fa3 Mon Sep 17 00:00:00 2001 From: James <91348155+FrogAi@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:00:00 -0700 Subject: [PATCH] Report a Bug or an Issue --- frogpilot/common/frogpilot_functions.py | 44 ++++++++++++++++++++- frogpilot/frogpilot_process.py | 7 +++- frogpilot/ui/qt/offroad/utilities.cc | 52 +++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/frogpilot/common/frogpilot_functions.py b/frogpilot/common/frogpilot_functions.py index 65251f8b..f9b8c5da 100644 --- a/frogpilot/common/frogpilot_functions.py +++ b/frogpilot/common/frogpilot_functions.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import io +import json import random +import requests import string import threading import time @@ -16,11 +19,50 @@ from openpilot.frogpilot.assets.theme_manager import ThemeManager from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd, use_konik_server from openpilot.frogpilot.common.frogpilot_variables import ( - ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPD_PATH, MAPS_PATH, THEME_SAVE_PATH, + DISCORD_WEBHOOK_URL_REPORT, ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPD_PATH, MAPS_PATH, THEME_SAVE_PATH, FrogPilotVariables, get_frogpilot_toggles ) +def capture_report(discord_user, report, frogpilot_toggles): + if not DISCORD_WEBHOOK_URL_REPORT: + return + + error_file_path = ERROR_LOGS_PATH / "error.txt" + error_content = "No error log found." + if error_file_path.exists(): + error_content = error_file_path.read_text()[:1000] + + message = ( + f"**🚨 New Error Report**\n\n" + f"**User:** `{discord_user}`\n\n" + f"**Report:**\n```{report}```\n\n" + f"**Error Log:**\n```{error_content}```\n\n" + f"**Toggle Settings:**" + ) + + try: + main_response = requests.post( + DISCORD_WEBHOOK_URL_REPORT, + data={"content": message}, + files={"file": ("frogpilot_toggles.json", io.BytesIO(json.dumps(frogpilot_toggles, indent=2).encode("utf-8")), "application/json")}, + timeout=10 + ) + main_response.raise_for_status() + + mention_response = requests.post( + DISCORD_WEBHOOK_URL_REPORT, + json={"content": "<@&1198482895342411846>"}, + timeout=10 + ) + mention_response.raise_for_status() + + except requests.exceptions.RequestException as exception: + print(f"Error sending Discord message: {exception}") + except Exception as exception: + print(f"Unexpected error: {exception}") + + def frogpilot_boot_functions(build_metadata, params): params_memory = Params(memory=True) diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py index 6fe11a12..325d35a2 100644 --- a/frogpilot/frogpilot_process.py +++ b/frogpilot/frogpilot_process.py @@ -10,7 +10,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.frogpilot.assets.theme_manager import THEME_COMPONENT_PARAMS, ThemeManager from openpilot.frogpilot.common.frogpilot_backups import backup_toggles -from openpilot.frogpilot.common.frogpilot_functions import update_maps, update_openpilot +from openpilot.frogpilot.common.frogpilot_functions import capture_report, update_maps, update_openpilot from openpilot.frogpilot.common.frogpilot_utilities import ThreadManager, flash_panda, is_url_pingable, lock_doors from openpilot.frogpilot.common.frogpilot_variables import ERROR_LOGS_PATH, FrogPilotVariables from openpilot.frogpilot.controls.frogpilot_planner import FrogPilotPlanner @@ -28,6 +28,11 @@ def check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles if params_memory.get_bool("FlashPanda"): thread_manager.run_with_lock(flash_panda, (params_memory)) + report_data = params_memory.get("IssueReported") + if report_data: + capture_report(report_data["DiscordUser"], report_data["Issue"], vars(frogpilot_toggles)) + params_memory.remove("IssueReported") + def transition_offroad(frogpilot_planner, theme_manager, thread_manager, time_validated, sm, params, frogpilot_toggles): params.put("LastGPSPosition", json.dumps(frogpilot_planner.gps_position)) diff --git a/frogpilot/ui/qt/offroad/utilities.cc b/frogpilot/ui/qt/offroad/utilities.cc index 1d74b61a..e74f77a1 100644 --- a/frogpilot/ui/qt/offroad/utilities.cc +++ b/frogpilot/ui/qt/offroad/utilities.cc @@ -68,6 +68,58 @@ FrogPilotUtilitiesPanel::FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent } addItem(forceStartedButton); + ButtonControl *reportIssueButton = new ButtonControl(tr("Report a Bug or an Issue"), tr("REPORT"), tr("Send a bug report so we can help fix the problem!")); + QObject::connect(reportIssueButton, &ButtonControl::clicked, [this]() { + if (!frogpilotUIState()->frogpilot_scene.online) { + ConfirmationDialog::alert(tr("Please connect to the internet before sending a report!"), this); + return; + } + + QStringList report_messages = { + tr("Acceleration feels harsh or jerky"), + tr("An alert was unclear and I'm not sure what it meant"), + tr("Braking is too sudden or uncomfortable"), + tr("I'm not sure if this is normal or a bug:"), + tr("My steering wheel buttons aren't working"), + tr("openpilot disengages when I don't expect it"), + tr("openpilot feels sluggish or slow to respond"), + tr("Something else (please describe)") + }; + + if (QFile::exists("/data/error_logs/error.txt")) { + report_messages.prepend(tr("I saw an alert that said \"openpilot crashed\"")); + } + + QString selected_issue = MultiOptionDialog::getSelection(tr("What's going on?"), report_messages, "", this); + if (selected_issue.isEmpty()) { + return; + } + + if (selected_issue.contains("crashed") || selected_issue.contains("not sure") || selected_issue.contains("Something else")) { + QString extra_input = InputDialog::getText(tr("Please describe what's happening"), this, tr("Send Report"), false, 10, "", 300).trimmed(); + if (extra_input.isEmpty()) { + return; + } + selected_issue += " — " + extra_input; + } + + QString discord_user = InputDialog::getText(tr("What's your Discord username?"), this, tr("Send Report"), false, -1, QString::fromStdString(params.get("DiscordUsername"))).trimmed(); + + QJsonObject reportData; + reportData["DiscordUser"] = discord_user; + reportData["Issue"] = selected_issue; + + params.putNonBlocking("DiscordUsername", discord_user.toStdString()); + params_memory.put("IssueReported", QJsonDocument(reportData).toJson(QJsonDocument::Compact).toStdString()); + + ConfirmationDialog::alert(tr("Report Sent! Thanks for letting us know!"), this); + }); + if (forceOpenDescriptions) { + reportIssueButton->showDescription(); + } + addItem(reportIssueButton); + reportIssueButton->setVisible(QString::fromStdString(params.get("GitRemote")).toLower() == "https://github.com/frogai/openpilot.git"); + ButtonControl *resetTogglesButton = new ButtonControl(tr("Reset Toggles to Default"), tr("RESET"), tr("Reset all toggles to their default values.")); QObject::connect(resetTogglesButton, &ButtonControl::clicked, [parent, resetTogglesButton, this]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all toggles to their default values?"), tr("Reset"), this)) {