Skip to content

Commit 0146039

Browse files
authored
Add one-click bug report bundle and prefilled issue forms (#8722)
## Summary Adds a **Report a Bug** button that saves the current game, collects the relevant logs, and writes a single zip the player can attach to an issue. The MegaMek, MegaMekLab and MekHQ buttons now open the issue form with the version, operating system and Java version already filled in. This is the MegaMek counterpart of MekHQ #8433. MegaMek already had the dialog itself (`BugReportDialog`, the four repo links, the Discord button), so this fills in the part that was missing: the player still had to find their save, work out which of the files in `logs/` mattered, and zip them by hand. Reporting is also now offered where a player actually is when something goes wrong, rather than only under the Help menu: on the error dialog raised for an uncaught exception, on the Commands button above the board, and on a button that sits in the same place in the phase display all game. ## What the player sees Before: the dialog told them to "save your game... best make a ZIP of it", then the issue form asked for MegaMek Suite Version, Operating System and Java Version - all required - and instructed them to open `megamek.log` in a text editor and copy them out of the header, with a screenshot showing what to look for. After: one button produces `MegaMek-BugReport-<timestamp>.zip` containing the save, the logs that matter, and a `system-info.txt`. Pressing "MegaMek" opens the issue form with those three fields already populated. In the dialog that button is larger than its neighbours and framed in yellow and red hazard stripes; in game it keeps the ordinary phase display skin and sits at the bottom of the Done column in every phase, so it does not have to be hunted for in a menu. <img width="712" height="382" alt="image" src="https://github.com/user-attachments/assets/0d113d69-4ed1-4ce4-9272-16bb21c99f79" /> https://github.com/user-attachments/assets/0f9ee990-a4e2-40fb-840c-5a2f932940de ## Changes 1. **`BugReportBundle`** decides what goes in the archive and writes it. It uses an explicit manifest rather than scanning the log folder, because that folder also accumulates one `gamelog*.html` combat report per game and a `Bot_*.mul` unit list per bot per game - a working install can hold hundreds of megabytes there. A plain `*.log` filter has the opposite problem: it would skip the combat report, which is usually the most useful single artifact in a MegaMek bug report. The archive is capped at the 25 MB GitHub allows for attachments, and anything dropped is named back to the player rather than silently omitted. 2. **`IssueReportUrl`** builds the prefilled URL. All three suite repositories use issue forms whose field ids are identical, so one builder serves them all and only the repository differs. `mm-data` has no template and is left alone. 3. **Save-completion callback on `AbstractClient`.** Saving is asynchronous: `/localsave` goes to the server, which serializes the game and streams it back as a `SEND_SAVEGAME` packet, so the archive cannot be built until that arrives. There is a 30 second timeout after which the archive is written with logs alone - see "Known limitation" below. 4. **Reporting offered from three new places.** The error dialog gains a Report a Bug button, gated on the error carrying a `Throwable`; the Commands menu gains a Report a Bug entry; and every phase display carries one at the bottom of the Done column, added in `StatusBarPhaseDisplay` after the phase's own contents so it lands below Skip where there is one and below Done where there is not. It is in the same place regardless of which button group is showing, so it is never a "More..." away. 5. **Copy System Data is now offered only where nothing gathers the files automatically.** In MegaMek the archive carries a `system-info.txt` and the repository buttons fill the same three details into the issue form, so the button duplicated work the player no longer has to do. MegaMekLab and MekHQ open this dialog without a packaging action and keep it, and the Help menu keeps the item in its own right. ## Two decisions worth reviewing **No customs export.** MekHQ #8433 threaded an `isBugReportPrep` flag through `Campaign.writeToXML` so custom units would be baked into the save. MegaMek needs no equivalent: `GameManagerSaveHelper` serializes the whole `Game` with XStream and `Server.loadGame` restores it without ever consulting `MekSummaryCache`, so customs are already inside the save. **The `labels` query parameter is deliberately not set, and should stay that way.** GitHub requires the visitor to hold permission for any action a query parameter performs, and serves a **404 page** when they do not. Ordinary players have no triage permission here, so adding a `labels` parameter would replace the issue form with a 404 for exactly the people this feature is for - while working fine for every maintainer who tested it. There is a regression test named for this (`IssueReportUrlTest.neverSetsTheLabelsParameter`) and the reasoning is in `IssueReportUrl`'s Javadoc. **Separately, and not caused by this PR:** while testing the above I noticed that bug reports filed through the web form arrive with **no label at all**. `bug_report.yml` declares `labels: [bug]`, but the repository's label is named `Bug`, and issue-template label matching is case sensitive - so it silently applies nothing. The RFE template, which declares the exactly-matching `(RFE) Enhancement`, does work. Compare #8718 (bug form, unlabelled) with #8709 (RFE form, labelled). That is a one-character fix in `.github/` and is up as #8724. ## Known limitation (not introduced here) The 30 second timeout exists to work around #8721: the server refuses a local save in a double-blind game with local saves disabled by sending a chat message and no packet, so `awaitingSave` is never cleared and the client can never finish shutting down. This PR works around it for the packager only; the underlying bug is filed separately and is not fixed here. ## Layering note `MMLogger` takes the error-dialog button as an installable hook rather than calling the dialog directly, because `megamek.logging` sits below the user interface and is shared with MegaMekLab, MekHQ and the headless dedicated server. Those install nothing and keep the plain OK dialog they have today. The hook carries both button labels, already localized, so no player-facing text lives in `megamek.logging`. ## Files Changed - `megamek/common/util/BugReportBundle.java` - new; manifest and zip writer, Swing-free so it is headless-testable - `megamek/common/util/IssueReportUrl.java` - new; prefilled issue-form URL builder - `megamek/client/ui/PackageBugReportAction.java` - new; the Swing action - chooser, save request, timeout, result dialog - `megamek/client/AbstractClient.java` - one-shot save-completion callback - `megamek/client/Client.java` - fire the callback on both `SEND_SAVEGAME` exit paths - `megamek/logging/MMLogger.java` - installable error-dialog button, offered only for errors carrying a `Throwable` - `megamek/client/ui/clientGUI/MegaMekGUI.java` - install the hook; resolve the running client on click - `megamek/client/ui/clientGUI/BugReportDialog.java` - button order, the enlarged reporting button and its hazard stripe border; prefilled repo links - `megamek/client/ui/clientGUI/GameCommandsMenu.java` - Report a Bug entry - `megamek/client/ui/panels/phaseDisplay/StatusBarPhaseDisplay.java` - the always-present button in the Done column - `megamek/client/ui/ShowBugReportDialogAction.java`, `CommonMenuBar.java`, `ClientGUI.java` - wiring - `BugReport.properties`, `messages.properties` - new keys; step 1 of the dialog text no longer says to zip by hand - `BugReportBundleTest.java`, `IssueReportUrlTest.java` - new; 15 tests ## Testing Unit tests: 15 new across the two test classes, covering the empty and missing log directory, manifest selection against a directory seeded with 40 `Bot_*.mul` files and three game logs, newest-game-log-only, save-at-archive-root, the size cap, and the labels regression. Full suite green at 15,072 tests, 0 failures. Playtested and confirmed: - The crash dialog. A deliberate `NullPointerException` on both the event dispatch thread and a background thread produces the "Uncaught Exception" dialog with the Report a Bug button, and it opens the helper. - Packaging in game. The archive was produced and its contents were complete, which exercises the asynchronous save round trip, the completion callback, and the manifest against a real log directory. - **The archived save loads back into MegaMek correctly.** The save the packager writes is a normal, complete save; being routed through the archive does not damage it. - Copy File to Clipboard, on Windows 11. - **The prefilled issue form, end to end against this repository.** A test report was filed from the in-game button and arrived with MegaMek Suite Version, Operating System and Java Version already populated, and with the generated archive attached. That test issue has since been closed. ## What is NOT proven yet - **The phase-display button has been compiled, not seen.** The Done column now asks for three button heights instead of two; whether that costs board space or fills the gap that was already under Skip needs a look in game. - The prefilled form has only been opened from an account with triage permission. The URL sets no permission-gated query parameter, so it should behave identically for everyone, but that has not been confirmed from a plain account. - The **double-blind save timeout** has not been exercised. It needs a game deliberately configured with Double Blind plus "Disable local saves when using double blind". - **Copy File to Clipboard is untested on macOS and Linux.** Where the desktop does not support it the button simply does nothing and Open Folder remains the guaranteed route. - The dedicated server path is unchanged by inspection - the hook is never installed there - but has not been run.
2 parents db06c61 + 60b5136 commit 0146039

17 files changed

Lines changed: 1734 additions & 35 deletions

megamek/resources/megamek/client/BugReport.properties

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,26 @@
2929
# <https://www.xbox.com/en-US/developers/rules> and it is not endorsed by or
3030
# affiliated with Microsoft.
3131
title=Report a Bug
32-
mainText=While we work hard to keep the program suite stable and running smoothly, things can still go wrong. Because this is a volunteer project without the funding or team for large-scale testing, we rely on players to report any issues they find. To do this, follow these steps:</p><blockquote><b> 1)</b> Save your game, campaign or unit. Best make a ZIP of it so you can upload it with the issue report. <br><b> 2) (Optional)</b> If you are on our Discord, it can help to ask whether the bug has already been reported. This may save you the effort of opening a report that ends up closed because the issue was already fixed. Duplicate reports are fine. We just want to save you time. Not a member of our Discord community? Consider pressing the 'Discord' button to join.<br><b> 3)</b> Press the appropriate button for the type of issue. You will need a GitHub account. We used to allow anonymous reporting, but it caused problems and had to be removed .<br><b> 4)</b> Fill out the issue report form, then upload the ZIP you created in step 1.</blockquote>
32+
mainText=While we work hard to keep the program suite stable and running smoothly, things can still go wrong. Because this is a volunteer project without the funding or team for large-scale testing, we rely on players to report any issues they find. To do this, follow these steps:</p><blockquote><b> 1)</b> Press the 'Report a Bug' button below. This saves your game and collects the logs into a single ZIP file for you. For a campaign problem, use 'Report a Bug' in MekHQ instead, so that the right campaign files are gathered for you. For a unit design problem, simply attach the unit file itself.<br><b> 2) (Optional)</b> If you are on our Discord, it can help to ask whether the bug has already been reported. This may save you the effort of opening a report that ends up closed because the issue was already fixed. Duplicate reports are fine. We just want to save you time. Not a member of our Discord community? Consider pressing the 'Discord' button to join.<br><b> 3)</b> Press the appropriate button for the type of issue. You will need a GitHub account, as we don't allow anonymous reporting.<br><b> 4)</b> Fill out the issue report form, then upload the ZIP you created in step 1.</blockquote>
3333
secondaryText=<h3 style="text-align:center">Which Bug Goes Where?</h3> <ul> <li>My bug was found during a scenario (i.e., a battle): <b>MegaMek</b> (ideally, we will need a save from before the scenario started) <li>My bug was found during unit customization: <b>MegaMekLab</b> <li>My bug was found in campaign management: <b>MekHQ</b> <li>I think a unit, planet, or other data is wrong: <b>Data</b> <li>I'm not sure: <b>MegaMek</b> (don't worry, we'll sort it out) </ul>
3434
discord.text=Join our Discord
3535
mm.text=MegaMek
3636
mml.text=MegaMekLab
3737
mhq.text=MekHQ
3838
mmData.text=Data
39+
package.reportBug=Report a Bug
40+
package.reportBug.tooltip=Opens the bug report helper, which can save the game and gather the logs for you.
41+
package.text=Report a Bug
42+
package.tooltip=Saves the current game, if one is running, and collects the logs into a single ZIP you can attach to an issue report.
43+
package.chooser.title=Save Bug Report
44+
package.chooser.filter=Bug Report Archives (*.zip)
45+
package.result.title=Bug Report Packaged
46+
package.result.included=Written to {0}, containing:
47+
package.result.skipped=Left out to stay under the 25 MB limit GitHub allows for attachments:
48+
package.result.next=Press 'MegaMek' to open the issue form. The archive is put on your clipboard at the same time, so you can paste it straight into the attachment box.
49+
package.result.copyFile=Copy File
50+
package.result.close=Close
51+
package.noGame=No game is running, so this archive holds the logs and your system information only.
52+
package.saveTimedOut=The server did not return a save in time, so this archive holds the logs only. Note that local saving is disabled in double-blind games.
53+
package.saveFailed=The save could not be written, so this archive holds the logs only.
54+
package.failed=Could not write the bug report archive. See megamek.log for details.

megamek/resources/megamek/client/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6566,6 +6566,8 @@ GameCommands.CheckBvPlayers.title=Check Battle Value (Players)
65666566
GameCommands.CheckBvPlayers.tooltip=Show each player's remaining battle value in the chat window
65676567
GameCommands.CheckBvTeams.title=Check Battle Value (Teams)
65686568
GameCommands.CheckBvTeams.tooltip=Show each team's remaining battle value in the chat window
6569+
GameCommands.ReportBug.title=Report a Bug
6570+
GameCommands.ReportBug.tooltip=Open the bug report helper, which can package this game and its logs into a single file to attach to an issue report
65696571
GameCommands.RequestGameMaster.title=Request Game Master Role
65706572
GameCommands.RequestGameMaster.tooltip=Ask to become the Game Master. During play every other player on a team must agree by choosing Allow Game Master.
65716573
GameCommands.GiveUpGameMaster.title=Give Up Game Master Role

megamek/src/megamek/client/AbstractClient.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2024-2025 The MegaMek Team. All Rights Reserved.
2+
* Copyright (C) 2024-2026 The MegaMek Team. All Rights Reserved.
33
*
44
* This file is part of MegaMek.
55
*
@@ -33,13 +33,16 @@
3333

3434
package megamek.client;
3535

36+
import java.io.File;
3637
import java.util.HashMap;
3738
import java.util.List;
3839
import java.util.Map;
3940
import java.util.TreeMap;
4041
import java.util.Vector;
42+
import java.util.function.Consumer;
4143
import javax.swing.SwingUtilities;
4244

45+
import jakarta.annotation.Nullable;
4346
import megamek.MMConstants;
4447
import megamek.SuiteConstants;
4548
import megamek.Version;
@@ -85,6 +88,8 @@ public abstract class AbstractClient implements IClient {
8588
protected boolean connected = false;
8689
protected boolean disconnectFlag = false;
8790
protected boolean awaitingSave = false;
91+
/** One-shot listener for the asynchronous local save; see {@link #setSaveCompletionCallback(Consumer)}. */
92+
private Consumer<File> saveCompletionCallback;
8893
protected final String host;
8994
protected final int port;
9095
private ConnectionHandler packetUpdate;
@@ -607,6 +612,39 @@ public void setAwaitingSave(boolean awaitingSave) {
607612
public boolean isAwaitingSave() {
608613
return awaitingSave;
609614
}
615+
616+
/**
617+
* Registers a one-shot callback to be run once a requested local save has actually landed on disk.
618+
*
619+
* <p>Saving is asynchronous in MegaMek: the request goes to the server as a chat command, the server serializes
620+
* the game, and the resulting file is streamed back to this client, which then writes it out. A caller that needs
621+
* the finished file - the bug report packager, for instance - therefore cannot simply read it after asking for
622+
* the save, because at that moment it does not yet exist.</p>
623+
*
624+
* <p>The callback is cleared as it fires, so it runs at most once per registration. Registering a new callback
625+
* replaces any previous one.</p>
626+
*
627+
* @param saveCompletionCallback invoked with the saved file, or with {@code null} if the save could not be
628+
* written; pass {@code null} to cancel a pending registration
629+
*/
630+
public void setSaveCompletionCallback(@Nullable Consumer<File> saveCompletionCallback) {
631+
this.saveCompletionCallback = saveCompletionCallback;
632+
}
633+
634+
/**
635+
* Runs and clears any registered save-completion callback. Safe to call when none is registered, and safe to call
636+
* more than once for a single save.
637+
*
638+
* @param savedFile the file that was written, or {@code null} if the save failed
639+
*/
640+
protected void fireSaveCompleted(@Nullable File savedFile) {
641+
Consumer<File> callback = saveCompletionCallback;
642+
saveCompletionCallback = null;
643+
if (callback != null) {
644+
callback.accept(savedFile);
645+
}
646+
}
647+
610648
/**
611649
* Custom connection Listener for AbstractClient
612650
*

megamek/src/megamek/client/Client.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,7 @@ protected boolean handleGameSpecificPacket(Packet packet) {
13401340
try {
13411341
if (!sDir.mkdir()) {
13421342
LOGGER.error("Failed to create savegames directory.");
1343+
fireSaveCompleted(null);
13431344
return true;
13441345
}
13451346
} catch (Exception ex) {
@@ -1360,6 +1361,10 @@ protected boolean handleGameSpecificPacket(Packet packet) {
13601361
LOGGER.error(ex, "Unable to save file {}", sFinalFile);
13611362
}
13621363
setAwaitingSave(false);
1364+
// Report the file only if it actually made it to disk; a failed or partial write must not be
1365+
// handed to a waiting caller as though it succeeded.
1366+
File savedFile = new File(localFile);
1367+
fireSaveCompleted(savedFile.isFile() ? savedFile : null);
13631368
break;
13641369
case LOAD_SAVEGAME:
13651370
String loadFile = packet.getStringValue(0);

0 commit comments

Comments
 (0)