From adb50e2405ed78df2d8d6622e074544b2198675d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?=
<1665721+pablomxnl@users.noreply.github.com>
Date: Thu, 23 Jan 2025 18:22:51 +0100
Subject: [PATCH] feat: Add error submitter for user feedback
---
.github/ISSUE_TEMPLATE/error_report.yml | 47 +++++++
.../error/QuarkusPluginIssueReporter.java | 125 ++++++++++++++++++
src/main/resources/META-INF/plugin.xml | 1 +
.../error/QuarkusPluginIssueReporterTest.java | 58 ++++++++
4 files changed, 231 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/error_report.yml
create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporter.java
create mode 100644 src/test/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporterTest.java
diff --git a/.github/ISSUE_TEMPLATE/error_report.yml b/.github/ISSUE_TEMPLATE/error_report.yml
new file mode 100644
index 000000000..ffd2d96fd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/error_report.yml
@@ -0,0 +1,47 @@
+name: Quarkus Tools Plugin Error Report
+description: "File a bug report for Quarkus Tool Plugin for IntelliJ"
+title: "bug: "
+labels:
+ - bug
+projects:
+ - redhat-developer/37
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for reporting this issue!! Please include as much information as possible in the Bug Description
+ **Please make sure to search for the title of this report** before submitting a new issue to avoid duplicates.
+ Tip: You can attach images, videos, or log files by dragging it into the text box.
+ - id: report
+ type: textarea
+ attributes:
+ label: Bug Description
+ description: Please describe the error. Include steps for reproducing the behavior and actual results.
+ value: |
+ ### Steps to reproduce
+ 1.
+ 1.
+
+ ### Actual results
+ Actual result
+
+ ### Screenshots or screencast
+ Please attach screenshots or a screencast that helps to understand the issue.
+ Make sure these screenshots or screencast have no sensitive information.
+ validations:
+ required: false
+ - id: info
+ type: textarea
+ attributes:
+ label: IDE Information
+ description: This information is automatically gathered.
+ validations:
+ required: true
+ - id: stacktrace
+ type: textarea
+ attributes:
+ label: Stacktrace and user message
+ description: |
+ This information is automatically gathered from IntelliJ Error Report Screen.
+ validations:
+ required: true
\ No newline at end of file
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporter.java b/src/main/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporter.java
new file mode 100644
index 000000000..84e680124
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporter.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.error;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.openapi.application.ApplicationInfo;
+import com.intellij.openapi.diagnostic.ErrorReportSubmitter;
+import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
+import com.intellij.openapi.diagnostic.SubmittedReportInfo;
+import com.intellij.openapi.extensions.PluginDescriptor;
+import com.intellij.openapi.util.NlsActions;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.Consumer;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.Component;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+public class QuarkusPluginIssueReporter extends ErrorReportSubmitter {
+
+ private static final String GITHUB_ISSUE_BASE_URL =
+ "https://github.com/redhat-developer/intellij-quarkus/issues/new?template=error_report.yml";
+
+ private static final String SYSTEM_INFO_TEMPLATE = """
+ | Attribute | Value |
+ |----------------------------|-------|
+ | **OS** | %s |
+ | **IDE** | %s |
+ | **JDK** | %s |
+ | **Quarkus Tools Version** | %s |
+ """;
+
+ public static final int MAX_URL_LENGTH = 8191;
+
+ final String ideVersion;
+ String pluginVersion;
+ final String operatingSystem;
+ final String jdkVersion;
+
+ public QuarkusPluginIssueReporter(){
+ ApplicationInfo applicationInfo = ApplicationInfo.getInstance();
+ operatingSystem = SystemInfo.getOsNameAndVersion() + "-" + SystemInfo.OS_ARCH;
+ ideVersion = String.join(" ", applicationInfo.getVersionName(),
+ applicationInfo.getFullVersion(), applicationInfo.getBuild().asString() );
+ jdkVersion = String.join(" ", System.getProperty("java.vm.name"),
+ SystemInfo.JAVA_VERSION, SystemInfo.JAVA_RUNTIME_VERSION, SystemInfo.JAVA_VENDOR );
+ }
+
+ @Override
+ public @NlsActions.ActionText @NotNull String getReportActionText() {
+ return "Create GitHub Issue";
+ }
+
+ @Override
+ public boolean submit(IdeaLoggingEvent @NotNull [] events,
+ @Nullable String userMessage,
+ @NotNull Component parentComponent,
+ @NotNull Consumer super SubmittedReportInfo> consumer) {
+ try {
+ IdeaLoggingEvent event = events[0];
+ String url = generateUrl(event.getThrowableText(), userMessage);
+ BrowserUtil.browse(url);
+ consumer.consume(new SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE));
+ } catch (Exception e) {
+ consumer.consume(new SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED));
+ return false;
+ }
+ return true;
+ }
+
+ String generateUrl(String throwableText, @Nullable String userMessage) {
+ PluginDescriptor pluginDescriptor = getPluginDescriptor();
+ pluginVersion = pluginDescriptor!=null? pluginDescriptor.getVersion() : "Unknown";
+ String systemInfo = encode(formatSystemInfo());
+ String titleLine = encode(throwableText.lines()
+ .findFirst().orElse("Unable to get exception message"));
+ String url = GITHUB_ISSUE_BASE_URL +"&title=" + titleLine + "&info=" + systemInfo + "&stacktrace=";
+ int available = MAX_URL_LENGTH - url.length();
+ String stackTrace = truncateStacktrace(throwableText, userMessage, available);
+ return url + stackTrace;
+ }
+
+ private String truncateStacktrace(String throwableText, @Nullable String userMessage, int available) {
+ String stackTrace = encode(formatStacktrace(throwableText,
+ Optional.ofNullable(userMessage).orElse("User didn't provide any message")));
+ if (stackTrace.length() > available) {
+ stackTrace = stackTrace.substring(0, available-1);
+ }
+ return stackTrace;
+ }
+
+ private String formatSystemInfo() {
+ return SYSTEM_INFO_TEMPLATE.formatted(operatingSystem, ideVersion, jdkVersion, pluginVersion);
+ }
+
+ private String formatStacktrace(String stackTrace, String userMessage) {
+ return """
+ **User message**: *%s*
+ **Stacktrace:**
+ ```
+ %s
+ ```
+ """.formatted(userMessage, stackTrace);
+ }
+
+ private String encode(final String text) {
+ return URLEncoder.encode(text, StandardCharsets.UTF_8);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index fe981e505..4c4ee8e14 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -472,6 +472,7 @@
+
diff --git a/src/test/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporterTest.java b/src/test/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporterTest.java
new file mode 100644
index 000000000..4063ccb64
--- /dev/null
+++ b/src/test/java/com/redhat/devtools/intellij/quarkus/error/QuarkusPluginIssueReporterTest.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.error;
+
+import com.intellij.testFramework.fixtures.BasePlatformTestCase;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static com.redhat.devtools.intellij.quarkus.error.QuarkusPluginIssueReporter.MAX_URL_LENGTH;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QuarkusPluginIssueReporterTest extends BasePlatformTestCase {
+ private QuarkusPluginIssueReporter reporter;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ reporter = new QuarkusPluginIssueReporter();
+ }
+
+ private static final String STACKTRACE =
+ """
+ java.lang.NullPointerException: Cannot invoke "javax.swing.JCheckBox.setSelected(boolean)" because "platformCheckbox" is null
+ at com.redhat.devtools.intellij.quarkus.projectWizard.QuarkusExtensionsStep.getComponent(QuarkusExtensionsStep.java:150)
+ at com.intellij.ide.wizard.AbstractWizard.updateStep(AbstractWizard.java:483)
+ """;
+
+ public void testEncodedUrlWithExceptionAndMessage() {
+ String userMessage = "I ran into this error with quarkus project wizard";
+ String url = URLDecoder.decode(reporter.generateUrl(STACKTRACE, userMessage), StandardCharsets.UTF_8);
+ assertThat(url).as("Stacktrace not found in decoded url").contains(STACKTRACE);
+ assertThat(url).as("userMessage not found in decoded url").contains(userMessage);
+ List systemInfo = List.of(reporter.ideVersion, reporter.jdkVersion,
+ reporter.operatingSystem, reporter.pluginVersion);
+ assertThat(systemInfo).allSatisfy(attribute -> assertThat(url).contains(attribute));
+ }
+
+ public void testLongStacktraceIsTruncated() {
+ String stackTrace = STACKTRACE.concat("A".repeat(MAX_URL_LENGTH));
+ String url = reporter.generateUrl(stackTrace, "Hallo");
+ String decodedUrl = URLDecoder.decode(url, StandardCharsets.UTF_8);
+ assertThat(decodedUrl).as("Initial Stacktrace not found in decoded url").contains(STACKTRACE);
+ assertThat(url.length()).isLessThan(MAX_URL_LENGTH);
+ }
+}