From 67cf82af7024be243b5e3b628b2a3b8ecfcb4b22 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Tue, 13 Aug 2024 13:14:07 -0400 Subject: [PATCH 1/7] refactored error reporting before adding new functionality --- .../nb_action/ReportABugMenuAction.java | 6 +- .../core/logging/LoggingUtils.java | 2 +- .../core/logging/NBExceptionHandler.java | 18 ++--- ...ueBox.java => ErrorReportDialogueBox.java} | 24 +++--- .../actions/context/ReportProblemAction.java | 74 ++++++------------- 5 files changed, 48 insertions(+), 76 deletions(-) rename modules/Core/src/main/java/org/janelia/workstation/core/util/{MailDialogueBox.java => ErrorReportDialogueBox.java} (86%) diff --git a/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java b/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java index e7d13f3ebe..5fdb2e8092 100644 --- a/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java +++ b/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java @@ -5,7 +5,7 @@ import javax.swing.AbstractAction; import org.janelia.workstation.core.logging.LoggingUtils; -import org.janelia.workstation.core.util.MailDialogueBox; +import org.janelia.workstation.core.util.ErrorReportDialogueBox; import org.janelia.workstation.integration.util.FrameworkAccess; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -27,9 +27,9 @@ public final class ReportABugMenuAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - String subject = LoggingUtils.getReportEmailSubject(false)+" -- Bug Report"; + String subject = LoggingUtils.getErrorReportSubject(false)+" -- Bug Report"; - MailDialogueBox popup = MailDialogueBox.newDialog(FrameworkAccess.getMainFrame()) + ErrorReportDialogueBox popup = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) .withTitle("Create A Ticket") .withPromptText("Problem Description:") .withEmailSubject(subject) diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/logging/LoggingUtils.java b/modules/Core/src/main/java/org/janelia/workstation/core/logging/LoggingUtils.java index cfbdfc7bd1..20005a182e 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/logging/LoggingUtils.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/logging/LoggingUtils.java @@ -8,7 +8,7 @@ */ public class LoggingUtils { - public static String getReportEmailSubject(boolean isAutoReport) { + public static String getErrorReportSubject(boolean isAutoReport) { AccessManager accessManager = AccessManager.getAccessManager(); diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java b/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java index 31ab25844e..f9a4826b29 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.janelia.workstation.core.api.AccessManager; import org.janelia.workstation.core.util.ConsoleProperties; -import org.janelia.workstation.core.util.MailDialogueBox; +import org.janelia.workstation.core.util.ErrorReportDialogueBox; import org.janelia.workstation.core.util.SystemInfo; import org.janelia.workstation.integration.util.FrameworkAccess; import org.slf4j.Logger; @@ -194,30 +194,30 @@ private void sendEmail(String stacktrace, boolean askForInput) { try { String firstLine = getSummary(stacktrace); log.info("Reporting exception: "+firstLine); - String subject = LoggingUtils.getReportEmailSubject(!askForInput)+" -- "+firstLine; + String subject = LoggingUtils.getErrorReportSubject(!askForInput)+" -- "+firstLine; - MailDialogueBox mailDialogueBox = MailDialogueBox.newDialog(FrameworkAccess.getMainFrame()) + ErrorReportDialogueBox errorReportDialogueBox = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) .withTitle("Create A Ticket") .withPromptText("If possible, please describe what you were doing when the error occurred:") .withEmailSubject(subject) .appendStandardPrefix(); if (askForInput) { - String problemDesc = mailDialogueBox.showPopup(); + String problemDesc = errorReportDialogueBox.showPopup(); if (problemDesc==null) { // User pressed cancel return; } else { - mailDialogueBox.append("\n\nProblem Description:\n"); - mailDialogueBox.append(problemDesc); + errorReportDialogueBox.append("\n\nProblem Description:\n"); + errorReportDialogueBox.append(problemDesc); } } - mailDialogueBox.append("\n\nStack Trace:\n"); - mailDialogueBox.append(stacktrace); + errorReportDialogueBox.append("\n\nStack Trace:\n"); + errorReportDialogueBox.append(stacktrace); - mailDialogueBox.sendEmail(); + errorReportDialogueBox.sendEmail(); } catch (Exception ex) { log.warn("Error sending exception email",ex); diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java similarity index 86% rename from modules/Core/src/main/java/org/janelia/workstation/core/util/MailDialogueBox.java rename to modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index a3ffecfcc7..c342699ec2 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -23,9 +23,9 @@ * @author kimmelr * @author Konrad Rokicki */ -public class MailDialogueBox { +public class ErrorReportDialogueBox { - private static final Logger log = LoggerFactory.getLogger(MailDialogueBox.class); + private static final Logger log = LoggerFactory.getLogger(ErrorReportDialogueBox.class); private static final String LOG_FILE_NAME = "messages.log"; @@ -36,40 +36,40 @@ public class MailDialogueBox { private StringBuffer body = new StringBuffer(); private JFrame parentFrame; - private MailDialogueBox(JFrame parentFrame) { + private ErrorReportDialogueBox(JFrame parentFrame) { this.parentFrame = parentFrame; } - public static MailDialogueBox newDialog(JFrame parentFrame) { - return new MailDialogueBox(parentFrame); + public static ErrorReportDialogueBox newDialog(JFrame parentFrame) { + return new ErrorReportDialogueBox(parentFrame); } - public MailDialogueBox withTitle(String title) { + public ErrorReportDialogueBox withTitle(String title) { this.title = title; return this; } - public MailDialogueBox withPromptText(String promptText) { + public ErrorReportDialogueBox withPromptText(String promptText) { this.promptText = promptText; return this; } - public MailDialogueBox withTextAreaBody(String initialBody) { + public ErrorReportDialogueBox withTextAreaBody(String initialBody) { this.initialBody = initialBody; return this; } - public MailDialogueBox withEmailSubject(String subject) { + public ErrorReportDialogueBox withEmailSubject(String subject) { this.subject = subject; return this; } - public MailDialogueBox append(String str) { + public ErrorReportDialogueBox append(String str) { body.append(str); return this; } - public MailDialogueBox appendStandardPrefix() { + public ErrorReportDialogueBox appendStandardPrefix() { append("\nSubject: ").append(AccessManager.getSubjectKey()); append("\nApplication: ").append(SystemInfo.appName).append(" v").append(SystemInfo.appVersion); append("\nServer: ").append(ConnectionMgr.getConnectionMgr().getConnectionString()); @@ -88,7 +88,7 @@ public MailDialogueBox appendStandardPrefix() { return this; } - public MailDialogueBox appendLine(String str) { + public ErrorReportDialogueBox appendLine(String str) { body.append(str).append("\n"); return this; } diff --git a/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java b/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java index dd027dac04..3092a5cb63 100644 --- a/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java +++ b/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java @@ -5,7 +5,9 @@ import org.janelia.workstation.common.actions.BaseContextualNodeAction; import org.janelia.workstation.core.activity_logging.ActivityLogHelper; import org.janelia.workstation.core.api.AccessManager; +import org.janelia.workstation.core.logging.LoggingUtils; import org.janelia.workstation.core.util.ConsoleProperties; +import org.janelia.workstation.core.util.ErrorReportDialogueBox; import org.janelia.workstation.core.util.MailHelper; import org.janelia.workstation.integration.util.FrameworkAccess; import org.openide.awt.ActionID; @@ -45,8 +47,7 @@ protected void processContext() { if (getNodeContext().isSingleObjectOfType(Sample.class)) { this.selectedObject = getNodeContext().getSingleObjectOfType(Sample.class); setEnabledAndVisible(true); - } - else { + } else { setEnabledAndVisible(false); } } @@ -61,66 +62,37 @@ public void performAction() { reportData(domainObject); JOptionPane.showMessageDialog(FrameworkAccess.getMainFrame(), - "Successfully reported problem with "+domainObject.getName(), + "Successfully reported problem with " + domainObject.getName(), "Data Problem Reported", JOptionPane.PLAIN_MESSAGE); - } - catch (Exception ex) { + } catch (Exception ex) { FrameworkAccess.handleException(ex); } } private void reportData(DomainObject domainObject) { - String fromEmail = ConsoleProperties.getString("console.FromEmail", null); - if (fromEmail==null) { - log.error("Cannot send exception report: no value for console.FromEmail is configured."); - return; - } + String subject = "Reported Data: " + domainObject.getName(); - String toEmail = ConsoleProperties.getString("console.HelpEmail", null); - if (toEmail==null) { - log.error("Cannot send exception report: no value for console.HelpEmail is configured."); - return; - } + // this dialog box will not be displayed; we do not call errorReportDialogueBox.showPopup() + ErrorReportDialogueBox errorReportDialogueBox = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) + .withTitle("not displayed") + .withPromptText("not displayed") + .withEmailSubject(subject); - DataReporter reporter = new DataReporter(fromEmail, toEmail); - reporter.reportData(domainObject, null); + errorReportDialogueBox.append(createEntityReport(domainObject)); + errorReportDialogueBox.sendEmail(); } - public static class DataReporter { - - private final String fromEmail; - private final String toEmail; + private String createEntityReport(DomainObject domainObject) { + StringBuilder sBuf = new StringBuilder(); - public DataReporter(String fromEmail, String toEmail) { - this.fromEmail = fromEmail; - this.toEmail = toEmail; - } + String user = AccessManager.getSubjectKey(); + sBuf.append("Reporting user: ").append(user).append("\n"); - private String createEntityReport(DomainObject domainObject, String annotation) { - StringBuilder sBuf = new StringBuilder(); - - String user = AccessManager.getSubjectKey(); - sBuf.append("Reporting user: ").append(user).append("\n"); - - sBuf.append("GUID: ").append(domainObject.getId().toString()).append("\n"); - sBuf.append("Type: ").append(domainObject.getType()).append("\n"); - sBuf.append("Owner: ").append(domainObject.getOwnerKey()).append("\n"); - sBuf.append("Name: ").append(domainObject.getName()).append("\n"); - if (annotation!=null) { - sBuf.append("Annotation: ").append(annotation).append("\n\n"); - } - return sBuf.toString(); - } - - public void reportData(DomainObject domainObject, String annotation) { - String subject = "Reported Data: " + domainObject.getName(); - if (annotation!=null) { - subject += " ("+annotation+")"; - } - String report = createEntityReport(domainObject, annotation); - MailHelper helper = new MailHelper(); - helper.sendEmail(fromEmail, toEmail, subject, report); - } + sBuf.append("GUID: ").append(domainObject.getId().toString()).append("\n"); + sBuf.append("Type: ").append(domainObject.getType()).append("\n"); + sBuf.append("Owner: ").append(domainObject.getOwnerKey()).append("\n"); + sBuf.append("Name: ").append(domainObject.getName()).append("\n"); + return sBuf.toString(); } -} \ No newline at end of file +} From d53150e6f40eb743d88d1f62785893f610de1ac8 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Fri, 30 Aug 2024 14:52:20 -0400 Subject: [PATCH 2/7] error reporting to GitHub basically working --- .../nb_action/ReportABugMenuAction.java | 4 +- .../core/logging/NBExceptionHandler.java | 18 +-- .../core/util/ErrorReportDialogueBox.java | 110 ++++++++++--- .../core/util/GitHubRestClient.java | 147 ++++++++++++++++++ .../actions/context/ReportProblemAction.java | 7 +- 5 files changed, 246 insertions(+), 40 deletions(-) create mode 100644 modules/Core/src/main/java/org/janelia/workstation/core/util/GitHubRestClient.java diff --git a/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java b/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java index 5fdb2e8092..95b29cf7ae 100644 --- a/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java +++ b/modules/CommonGUI/src/main/java/org/janelia/workstation/common/nb_action/ReportABugMenuAction.java @@ -32,14 +32,14 @@ public void actionPerformed(ActionEvent e) { ErrorReportDialogueBox popup = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) .withTitle("Create A Ticket") .withPromptText("Problem Description:") - .withEmailSubject(subject) + .withSubject(subject) .appendStandardPrefix() .append("\n\nMessage:\n"); String desc = popup.showPopup(); if (desc!=null) { popup.appendLine(desc); - popup.sendEmail(); + popup.sendReport(); } } diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java b/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java index f9a4826b29..c239b1a442 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/logging/NBExceptionHandler.java @@ -117,10 +117,10 @@ private synchronized void autoSendNovelExceptions() { // Allow one exception report every cooldown cycle. Our RateLimiter allows one access every // second, so we need to acquire a cooldown's worth of locks. if (!rateLimiter.tryAcquire(COOLDOWN_TIME_SEC, 0, TimeUnit.SECONDS)) { - log.warn("Exception reports exceeded email rate limit. Omitting auto-send of: {}", firstLine); + log.warn("Exception reports exceeded rate limit. Omitting auto-send of: {}", firstLine); return; } - sendEmail(st, false); + sendReport(st, false); } else { int count = exceptionCounts.count(traceHash); @@ -186,10 +186,10 @@ public void actionPerformed(ActionEvent e) { SwingUtilities.windowForComponent(newFunctionButton).setVisible(false); // Due to the way the NotifyExcPanel works, this might not be the exception the user is currently looking at! // Maybe it's better than nothing if it's right 80% of the time? - sendEmail(ExceptionUtils.getStackTrace(throwable), true); + sendReport(ExceptionUtils.getStackTrace(throwable), true); } - private void sendEmail(String stacktrace, boolean askForInput) { + private void sendReport(String stacktrace, boolean askForInput) { try { String firstLine = getSummary(stacktrace); @@ -199,7 +199,7 @@ private void sendEmail(String stacktrace, boolean askForInput) { ErrorReportDialogueBox errorReportDialogueBox = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) .withTitle("Create A Ticket") .withPromptText("If possible, please describe what you were doing when the error occurred:") - .withEmailSubject(subject) + .withSubject(subject) .appendStandardPrefix(); if (askForInput) { @@ -217,14 +217,14 @@ private void sendEmail(String stacktrace, boolean askForInput) { errorReportDialogueBox.append("\n\nStack Trace:\n"); errorReportDialogueBox.append(stacktrace); - errorReportDialogueBox.sendEmail(); + errorReportDialogueBox.sendReport(); } catch (Exception ex) { - log.warn("Error sending exception email",ex); - if (askForInput) { // JW-25430: Only show this message if the email was initiated by the user + log.warn("Error sending exception report",ex); + if (askForInput) { // JW-25430: Only show this message if the report was initiated by the user JOptionPane.showMessageDialog(FrameworkAccess.getMainFrame(), "Your message was NOT able to be sent to our support staff. " - + "Please contact your support representative.", "Error sending email", JOptionPane.ERROR_MESSAGE); + + "Please contact your support representative.", "Error sending report", JOptionPane.ERROR_MESSAGE); } } } diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index c342699ec2..c714b4f5d7 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -18,10 +18,8 @@ import org.slf4j.LoggerFactory; /** - * Creates a dialog to get the user's input and then sends an email to JIRA to create a ticket. - * - * @author kimmelr - * @author Konrad Rokicki + * Creates a dialog to get the user's input and then sends a report to create a ticket, + * either via email or via GitHub API. */ public class ErrorReportDialogueBox { @@ -29,6 +27,11 @@ public class ErrorReportDialogueBox { private static final String LOG_FILE_NAME = "messages.log"; + // for GitHub reporting + private static final String ISSUES_BRANCH = "issues"; + private static final String ATTACHMENTS_FOLDER = "attachments"; + + private String subject = ""; private String initialBody = ""; private String title = ""; @@ -59,7 +62,7 @@ public ErrorReportDialogueBox withTextAreaBody(String initialBody) { return this; } - public ErrorReportDialogueBox withEmailSubject(String subject) { + public ErrorReportDialogueBox withSubject(String subject) { this.subject = subject; return this; } @@ -106,46 +109,105 @@ public String showPopup() { desc = textArea.getText() + "\n"; return desc; } - - public void sendEmail() { + public void sendReport() { + String method = ConsoleProperties.getString("console.ErrorReportingMethod", null); + if (method == null) { + log.error("Cannot send error report; no value for console.ErrorReportingMethod is configured"); + return; + } + + if (method.equals("email")) { + sendEmail(); + } else if (method.equals("github")) { + sendGitHub(); + } else { + log.error("Cannot send error report; unknown value {} for console.ErrorReportingMethod", method); + return; + } + } + + public void sendEmail() { String fromEmail = ConsoleProperties.getString("console.FromEmail", null); if (fromEmail==null) { log.error("Cannot send exception report: no value for console.FromEmail is configured."); return; } - String toEmail = ConsoleProperties.getString("console.HelpEmail", null); if (toEmail==null) { log.error("Cannot send exception report: no value for console.HelpEmail is configured."); return; } + String filename = ""; + File logfile = getLogfile(); + if (logfile != null) { + filename = AccessManager.getSubjectName()+"_"+LOG_FILE_NAME; + } + + log.info("Sending email from {} to {} with attachment {}", fromEmail, toEmail, logfile); + + MailHelper helper = new MailHelper(); + helper.sendEmail(fromEmail, toEmail, subject, body.toString(), logfile, filename); + } + + public void sendGitHub() { + String githubURL = ConsoleProperties.getString("console.GitHubErrorProjectURL", null); + if (githubURL==null) { + log.error("Cannot send exception report: no value for console.GitHubErrorProjectURL is configured."); + return; + } + String githubToken = ConsoleProperties.getString("console.GitHubErrorProjectAccessToken", null); + if (githubToken==null) { + log.error("Cannot send exception report: no value for console.GitHubErrorProjectAccessToken is configured."); + return; + } + + File logfile = getLogfile(); + log.info("Creating GitHub issue in project {} with logfile attachment {}", githubURL, logfile); + + // note that our logfiles are far too long for a GitHub issue (65k char limit), + // so this is a three step process: create issue with body text, upload the + // logfile to the repo, then create a comment with a link to the logfile + + GitHubRestClient client = new GitHubRestClient(); + + int issueNumber = client.createIssue(subject, body.toString()); + if (issueNumber <= 0) { + log.error("GitHub issue not created for error report"); + return; + } + + String path = ATTACHMENTS_FOLDER + "/issue-" + issueNumber + "-" + LOG_FILE_NAME; + String permalink = client.uploadLogFile(ISSUES_BRANCH, logfile, path); + if (permalink.isEmpty()) { + log.error("Logfile not uploaded or error in generating permalink"); + return; + } + + String comment = "[Link to uploaded log file.](" + permalink + ")"; + boolean success = client.addComment(issueNumber, comment); + if (!success) { + log.error("Failed to add comment to GitHub issue with permalink to log."); + } + } + + private File getLogfile() { + // Flush all long handlers so that we have a complete log file - java.util.logging.Logger logger = java.util.logging.Logger.getLogger(""); + java.util.logging.Logger logger = java.util.logging.Logger.getLogger(""); for (java.util.logging.Handler handler : logger.getHandlers()) { handler.flush(); } - - String filename = null; + File logDir = new File(Places.getUserDirectory(), "var/log"); File logfile = new File(logDir, LOG_FILE_NAME); if (!logfile.canRead()) { - log.info("Can't read log file at "+logfile.getAbsolutePath()); + log.info("Can't read log file at " + logfile.getAbsolutePath()); logfile = null; } - else { - filename = AccessManager.getSubjectName()+"_"+LOG_FILE_NAME; - } - - log.info("Sending email from {} to {} with attachment {}", fromEmail, toEmail, logfile); - - MailHelper helper = new MailHelper(); - helper.sendEmail(fromEmail, toEmail, subject, body.toString(), logfile, filename); - // TODO: this should only be shown when the user manually reports a bug -// JOptionPane.showMessageDialog( -// FrameworkAccess.getMainFrame(), "Bug was reported successfully", "Success", -// JOptionPane.INFORMATION_MESSAGE); + return logfile; } + } diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/GitHubRestClient.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/GitHubRestClient.java new file mode 100644 index 0000000000..a6a55dd863 --- /dev/null +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/GitHubRestClient.java @@ -0,0 +1,147 @@ +package org.janelia.workstation.core.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.client.*; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Scanner; + +public class GitHubRestClient { + private static final Logger logger = LoggerFactory.getLogger(GitHubRestClient.class); + + private final String projectURL; + private final String accessToken; + + private Client client; + + public GitHubRestClient() { + this.projectURL = ConsoleProperties.getInstance().getProperty("console.GitHubErrorProjectURL"); + this.accessToken = ConsoleProperties.getInstance().getProperty("console.GitHubErrorProjectAccessToken"); + logger.debug("Using project URL: {}", this.projectURL); + + client = ClientBuilder.newClient(); + } + + private Invocation.Builder getBuilder(String path) { + WebTarget baseTarget = client.target(projectURL); + WebTarget pathTarget = baseTarget.path(path); + return pathTarget.request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + } + + // this method is for testing and debugging + public List getIssueList() { + List issues = new ArrayList<>(); + + Response response = getBuilder("issues").get(); + if (response.getStatus() != Response.Status.OK.getStatusCode()) { + logger.error("Error getting issues list; status {}, body {}", response.getStatus(), response.readEntity(String.class)); + return issues; + } + + String json = response.readEntity(String.class); + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode nodes = mapper.readValue(json, JsonNode.class); + for (JsonNode node: nodes) { + issues.add(node.at("/title").asText()); + } + } catch (JsonProcessingException e) { + logger.error("Error parsing json for issues list"); + return issues; + } + + return issues; + } + + public int createIssue(String title, String body) { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode newIssue = mapper.createObjectNode(); + newIssue.put("title", title); + newIssue.put("body", body); + + Response response = getBuilder("issues").post(Entity.json(newIssue)); + if (response.getStatus() != Response.Status.CREATED.getStatusCode()) { + logger.error("Error creating new issue; status {}, body {}", response.getStatus(), response.readEntity(String.class)); + return 0; + } + + String json = response.readEntity(String.class); + try { + ObjectMapper mapper2 = new ObjectMapper(); + JsonNode node = mapper2.readTree(json); + if (!node.has("number")) { + logger.error("Returned json doesn't contain issue number"); + return 0; + } + return Integer.parseInt(node.get("number").asText()); + } catch (JsonProcessingException e) { + logger.error("Error parsing json for created issue"); + return 0; + } + } + + public String uploadLogFile(String branch, File logfile, String path) { + String unencodedLog = ""; + try (Scanner scanner = new Scanner(logfile)) { + unencodedLog = scanner.useDelimiter("\\A").next(); + } catch (IOException e) { + logger.error("Error reading log file"); + return ""; + } + String encodedLog = Base64.getEncoder().encodeToString(unencodedLog.getBytes()); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode upload = mapper.createObjectNode(); + upload.put("message", "uploaded attachment"); + upload.put("branch", branch); + upload.put("content", encodedLog); + + Response response = getBuilder("contents/" + path).put(Entity.json(upload)); + if (response.getStatus() != Response.Status.CREATED.getStatusCode()) { + logger.error("Error uploading logfile; status {}, body {}", response.getStatus(), response.readEntity(String.class)); + return ""; + } + + // generate permalink to the uploaded file + String json = response.readEntity(String.class); + String permalink = ""; + try { + ObjectMapper mapper2 = new ObjectMapper(); + JsonNode tree = mapper2.readTree(json); + String sha = tree.at("/commit/sha").asText(); + permalink = projectURL.replace("api.", "").replace("/repos", "") + + "/blob/" + sha + "/" + path; + } catch (JsonProcessingException e) { + logger.error("Error parsing json from logfile upload"); + return ""; + } + return permalink; + } + + public boolean addComment(int issueId, String comment) { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode commentNode = mapper.createObjectNode(); + commentNode.put("body", comment); + + Response response = getBuilder("issues/" + issueId + "/comments").post(Entity.json(commentNode)); + if (response.getStatus() != Response.Status.CREATED.getStatusCode()) { + logger.error("Error adding comment; status {}, body {}", response.getStatus(), response.readEntity(String.class)); + return false; + } else { + return true; + } + } + +} diff --git a/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java b/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java index 3092a5cb63..bc431d5f8d 100644 --- a/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java +++ b/modules/LMDataBrowser/src/main/java/org/janelia/workstation/lm/actions/context/ReportProblemAction.java @@ -5,10 +5,7 @@ import org.janelia.workstation.common.actions.BaseContextualNodeAction; import org.janelia.workstation.core.activity_logging.ActivityLogHelper; import org.janelia.workstation.core.api.AccessManager; -import org.janelia.workstation.core.logging.LoggingUtils; -import org.janelia.workstation.core.util.ConsoleProperties; import org.janelia.workstation.core.util.ErrorReportDialogueBox; -import org.janelia.workstation.core.util.MailHelper; import org.janelia.workstation.integration.util.FrameworkAccess; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -77,10 +74,10 @@ private void reportData(DomainObject domainObject) { ErrorReportDialogueBox errorReportDialogueBox = ErrorReportDialogueBox.newDialog(FrameworkAccess.getMainFrame()) .withTitle("not displayed") .withPromptText("not displayed") - .withEmailSubject(subject); + .withSubject(subject); errorReportDialogueBox.append(createEntityReport(domainObject)); - errorReportDialogueBox.sendEmail(); + errorReportDialogueBox.sendReport(); } private String createEntityReport(DomainObject domainObject) { From 55a44960dac34ffe54d859dcf92d38f3f4302fd9 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Mon, 9 Sep 2024 14:03:13 -0400 Subject: [PATCH 3/7] use [JW] prefix for GitHub reports, too --- .../janelia/workstation/core/util/ErrorReportDialogueBox.java | 4 ++++ .../java/org/janelia/workstation/core/util/MailHelper.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index c714b4f5d7..b031951246 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -31,6 +31,7 @@ public class ErrorReportDialogueBox { private static final String ISSUES_BRANCH = "issues"; private static final String ATTACHMENTS_FOLDER = "attachments"; + private static final String SUBJECT_PREFIX = "[JW] "; private String subject = ""; private String initialBody = ""; @@ -63,6 +64,9 @@ public ErrorReportDialogueBox withTextAreaBody(String initialBody) { } public ErrorReportDialogueBox withSubject(String subject) { + if (!subject.startsWith(SUBJECT_PREFIX)) { + subject = SUBJECT_PREFIX + subject; + } this.subject = subject; return this; } diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java index 41b0dd7de3..1a93a06b46 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java @@ -64,7 +64,7 @@ protected PasswordAuthentication getPasswordAuthentication() { Message message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(RecipientType.TO, InternetAddress.parse(to)); - message.setSubject("[JW] " + subject); + message.setSubject(subject); BodyPart messagePart = new MimeBodyPart(); messagePart.setText(bodyText); Multipart multipart = new MimeMultipart(); From cbd2993f73c344ae1bcb6030e5b94644778fb395 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Fri, 18 Oct 2024 10:28:33 -0400 Subject: [PATCH 4/7] default to email reporting if option isn't set (backward compatible) --- .../workstation/core/util/ErrorReportDialogueBox.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index b031951246..8deafacb09 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -117,8 +117,9 @@ public String showPopup() { public void sendReport() { String method = ConsoleProperties.getString("console.ErrorReportingMethod", null); if (method == null) { - log.error("Cannot send error report; no value for console.ErrorReportingMethod is configured"); - return; + // "email" was our original implementation, so if unset, default to "email" for + // backward compatibility + method = "email"; } if (method.equals("email")) { From 7d4836db8406358140f6e51c24a28cbfeec34d5a Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Fri, 28 Feb 2025 13:19:34 -0500 Subject: [PATCH 5/7] fixed embarrassing typo --- .../org/janelia/workstation/infopanel/WorkspaceInfoPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ViewerInfoPanel/src/main/java/org/janelia/workstation/infopanel/WorkspaceInfoPanel.java b/modules/ViewerInfoPanel/src/main/java/org/janelia/workstation/infopanel/WorkspaceInfoPanel.java index 4ac4b00e2f..1c8932cbc6 100644 --- a/modules/ViewerInfoPanel/src/main/java/org/janelia/workstation/infopanel/WorkspaceInfoPanel.java +++ b/modules/ViewerInfoPanel/src/main/java/org/janelia/workstation/infopanel/WorkspaceInfoPanel.java @@ -58,7 +58,7 @@ public void loadWorkspace(TmWorkspace workspace) { */ private void updateMetaData(final TmWorkspace workspace) { if (TmModelManager.getInstance().getCurrentSample() == null) { - setSampleName("(no sample"); + setSampleName("(no sample)"); setWorkspaceName("(no workspace)", false); return; } else { From a605221b04e2fc84416985295d015c49836b82a9 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Tue, 25 Mar 2025 14:21:15 -0400 Subject: [PATCH 6/7] checkpoint: dialog built, but toggle not implemented --- .../core/util/ErrorReportDialogueBox.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index 8deafacb09..2d32e694e4 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -40,6 +40,9 @@ public class ErrorReportDialogueBox { private StringBuffer body = new StringBuffer(); private JFrame parentFrame; + // flag to track whether we've reported an error about reporting errors already + private static boolean reportedErrorReportingError = false; + private ErrorReportDialogueBox(JFrame parentFrame) { this.parentFrame = parentFrame; } @@ -122,6 +125,7 @@ public void sendReport() { method = "email"; } + if (method.equals("email")) { sendEmail(); } else if (method.equals("github")) { @@ -179,22 +183,29 @@ public void sendGitHub() { int issueNumber = client.createIssue(subject, body.toString()); if (issueNumber <= 0) { - log.error("GitHub issue not created for error report"); + String message = "GitHub issue not created for error report"; + log.error(message); + reportErrorReportingError(message); return; } String path = ATTACHMENTS_FOLDER + "/issue-" + issueNumber + "-" + LOG_FILE_NAME; String permalink = client.uploadLogFile(ISSUES_BRANCH, logfile, path); if (permalink.isEmpty()) { - log.error("Logfile not uploaded or error in generating permalink"); + String message = "Logfile not uploaded or error in generating permalink"; + log.error(message); + reportErrorReportingError(message); return; } String comment = "[Link to uploaded log file.](" + permalink + ")"; boolean success = client.addComment(issueNumber, comment); if (!success) { - log.error("Failed to add comment to GitHub issue with permalink to log."); + String message = "Failed to add comment to GitHub issue with permalink to log."; + log.error(message); + reportErrorReportingError(message); } + } private File getLogfile() { @@ -215,4 +226,24 @@ private File getLogfile() { return logfile; } + private void reportErrorReportingError(String message) { + if (reportedErrorReportingError) { + return; + } + reportedErrorReportingError = true; + + Object[] buttons = {"Silence", "Continue"}; + message = "An error was encountered while reporting previous error:\n\n" + message + + "\n\nPlease report this error to the site admins, as it cannot be reported automatically!" + + "\n\nContinue reporting this class of error, or Silence reports for this session?"; + JOptionPane.showOptionDialog(null, + message, + "Error while reporting error!", + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + buttons, + buttons[0] + ); + } } From 32a0af4ea4a05a5fef517b576bf882492b357156 Mon Sep 17 00:00:00 2001 From: "Donald J. Olbris" Date: Thu, 27 Mar 2025 10:32:10 -0400 Subject: [PATCH 7/7] finished implementing silenceable user notification on error reporting failures --- .../core/util/ErrorReportDialogueBox.java | 39 ++++++++++++------- .../workstation/core/util/MailHelper.java | 9 +++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java index 2d32e694e4..61d852afc2 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/ErrorReportDialogueBox.java @@ -41,7 +41,7 @@ public class ErrorReportDialogueBox { private JFrame parentFrame; // flag to track whether we've reported an error about reporting errors already - private static boolean reportedErrorReportingError = false; + private static boolean silenceErrorReportingFailure = false; private ErrorReportDialogueBox(JFrame parentFrame) { this.parentFrame = parentFrame; @@ -125,7 +125,6 @@ public void sendReport() { method = "email"; } - if (method.equals("email")) { sendEmail(); } else if (method.equals("github")) { @@ -157,7 +156,11 @@ public void sendEmail() { log.info("Sending email from {} to {} with attachment {}", fromEmail, toEmail, logfile); MailHelper helper = new MailHelper(); - helper.sendEmail(fromEmail, toEmail, subject, body.toString(), logfile, filename); + boolean result = helper.sendEmail(fromEmail, toEmail, subject, body.toString(), logfile, filename); + if (!result) { + String message = "Error reporting email not sent or error in sending email"; + reportErrorReportingFailure(message); + } } public void sendGitHub() { @@ -185,16 +188,16 @@ public void sendGitHub() { if (issueNumber <= 0) { String message = "GitHub issue not created for error report"; log.error(message); - reportErrorReportingError(message); + reportErrorReportingFailure(message); return; } String path = ATTACHMENTS_FOLDER + "/issue-" + issueNumber + "-" + LOG_FILE_NAME; String permalink = client.uploadLogFile(ISSUES_BRANCH, logfile, path); if (permalink.isEmpty()) { - String message = "Logfile not uploaded or error in generating permalink"; + String message = "Logfile not uploaded to GitHub or error in generating permalink"; log.error(message); - reportErrorReportingError(message); + reportErrorReportingFailure(message); return; } @@ -203,7 +206,7 @@ public void sendGitHub() { if (!success) { String message = "Failed to add comment to GitHub issue with permalink to log."; log.error(message); - reportErrorReportingError(message); + reportErrorReportingFailure(message); } } @@ -226,24 +229,30 @@ private File getLogfile() { return logfile; } - private void reportErrorReportingError(String message) { - if (reportedErrorReportingError) { + private void reportErrorReportingFailure(String message) { + if (silenceErrorReportingFailure) { return; } - reportedErrorReportingError = true; Object[] buttons = {"Silence", "Continue"}; - message = "An error was encountered while reporting previous error:\n\n" + message + - "\n\nPlease report this error to the site admins, as it cannot be reported automatically!" + - "\n\nContinue reporting this class of error, or Silence reports for this session?"; - JOptionPane.showOptionDialog(null, + message = "Error reporting failed:\n\n" + message + + "\n\nPlease report this issue to the site admins, as it cannot be reported automatically!" + + "\n\nContinue to show this dialog when error reporting fails, or Silence these dialogs for this session?"; + Object response = JOptionPane.showOptionDialog(null, message, - "Error while reporting error!", + "Error not reported!", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, buttons, buttons[0] ); + int index = (int) response; + if (index < 0 || index >= buttons.length) { + return; + } + if (buttons[index].equals("Silence")) { + silenceErrorReportingFailure = true; + } } } diff --git a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java b/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java index 1a93a06b46..1471efa56e 100644 --- a/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java +++ b/modules/Core/src/main/java/org/janelia/workstation/core/util/MailHelper.java @@ -23,11 +23,11 @@ public class MailHelper { public MailHelper() { } - public void sendEmail(String from, String to, String subject, String bodyText) { - this.sendEmail(from, to, subject, bodyText, null, null); + public boolean sendEmail(String from, String to, String subject, String bodyText) { + return this.sendEmail(from, to, subject, bodyText, null, null); } - public void sendEmail(String from, String to, String subject, String bodyText, File attachedFile, String filename) { + public boolean sendEmail(String from, String to, String subject, String bodyText, File attachedFile, String filename) { try { String mailServer = ConsoleProperties.getString("console.MailServer"); String mailUser = ConsoleProperties.getString("console.MailUser", ""); @@ -88,9 +88,12 @@ protected PasswordAuthentication getPasswordAuthentication() { log.info(" To: " + to); log.info(" Body: " + bodyText); + return true; + } catch (MessagingException var13) { log.error("Error sending email", var13); + return false; } }