From c451006cc9fc926ca347189951baa94f4032c5c4 Mon Sep 17 00:00:00 2001 From: Florin Patan Date: Sun, 26 Oct 2014 11:12:17 +0100 Subject: [PATCH 1/2] Add feedback submission facility to the plugin --- src/META-INF/plugin.xml | 2 + src/ro/redeul/google/go/GoBundle.properties | 4 + .../diagnostics/error/AnonymousFeedback.java | 120 +++++++++++++++ .../error/AnonymousFeedbackTask.java | 75 ++++++++++ .../go/diagnostics/error/ErrorReporter.java | 138 ++++++++++++++++++ .../go/diagnostics/error/IdeaITNProxy.java | 70 +++++++++ 6 files changed, 409 insertions(+) create mode 100644 src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java create mode 100644 src/ro/redeul/google/go/diagnostics/error/AnonymousFeedbackTask.java create mode 100644 src/ro/redeul/google/go/diagnostics/error/ErrorReporter.java create mode 100644 src/ro/redeul/google/go/diagnostics/error/IdeaITNProxy.java diff --git a/src/META-INF/plugin.xml b/src/META-INF/plugin.xml index c8f77e4e4f..e0caa1a43f 100644 --- a/src/META-INF/plugin.xml +++ b/src/META-INF/plugin.xml @@ -309,6 +309,8 @@ + + diff --git a/src/ro/redeul/google/go/GoBundle.properties b/src/ro/redeul/google/go/GoBundle.properties index 3d25ae7706..9c850bfce0 100644 --- a/src/ro/redeul/google/go/GoBundle.properties +++ b/src/ro/redeul/google/go/GoBundle.properties @@ -147,3 +147,7 @@ warning.function.return.call.types.mismatch=The returned expressions don't match warning.index.invalid=Invalid index {0} {1} go.run.executablename=Output e&xecutable name\: go.run.package=Ma&in package directory\: +go.error.report.message=Error Submitting Feedback: {0}
\ + Consider creating an issue at \ + Github Issue Tracker +go.error.report.action=&Report on Github diff --git a/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java new file mode 100644 index 0000000000..4b88d77d32 --- /dev/null +++ b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java @@ -0,0 +1,120 @@ +package ro.redeul.google.go.diagnostics.error; + +import com.google.gson.Gson; +import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.openapi.extensions.PluginId; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; + +public class AnonymousFeedback { + + public AnonymousFeedback() { + } + + public static String sendFeedback( + AnonymousFeedback.HttpConnectionFactory httpConnectionFactory, + LinkedHashMap environmentDetails) throws IOException { + + sendFeedback(httpConnectionFactory, convertToGithubIssueFormat(environmentDetails)); + + return Long.toString(System.currentTimeMillis()); + } + + private static byte[] convertToGithubIssueFormat(LinkedHashMap environmentDetails) { + LinkedHashMap result = new LinkedHashMap(5); + result.put("title", "[auto-generated] Crash in plugin"); + result.put("body", generateGithubIssueBody(environmentDetails)); + + return ((new Gson()).toJson(result)).getBytes(Charset.forName("UTF-8")); + } + + private static String generateGithubIssueBody(LinkedHashMap body) { + String errorDescription = body.get("error.description"); + if (errorDescription == null) { + errorDescription = ""; + } + body.remove("error.description"); + + String errorMessage = body.get("error.message"); + if (errorMessage == null || errorMessage.isEmpty()) { + errorMessage = "invalid error"; + } + body.remove("error.message"); + + String stackTrace = body.get("error.stacktrace"); + if (stackTrace == null || stackTrace.isEmpty()) { + stackTrace = "invalid stacktrace"; + } + body.remove("error.stacktrace"); + + String result = ""; + + if (!errorDescription.isEmpty()) { + result += errorDescription + "\n\n"; + } + + for (Map.Entry entry : body.entrySet()) { + result += entry.getKey() + ": " + entry.getValue() + "\n"; + } + + result += "\n```\n" + stackTrace + "\n```\n"; + + result += "\n```\n" + errorMessage + "\n```"; + + return result; + } + + private static void sendFeedback(AnonymousFeedback.HttpConnectionFactory httpConnectionFactory, byte[] payload) throws IOException { + String url = "https://api.github.com/repos/go-lang-plugin-org/go-lang-idea-plugin/issues"; + String userAgent = "golang IntelliJ IDEA plugin"; + + IdeaPluginDescriptorImpl pluginDescriptor = (IdeaPluginDescriptorImpl) PluginManager.getPlugin(PluginId.getId("ro.redeul.google.go")); + if (pluginDescriptor != null) { + String name = pluginDescriptor.getName(); + String version = pluginDescriptor.getVersion(); + userAgent = name + " (" + version + ")"; + } + + HttpURLConnection httpURLConnection = connect(httpConnectionFactory, url); + httpURLConnection.setDoOutput(true); + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setRequestProperty("User-Agent", userAgent); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Authorization", "token xxxx"); + OutputStream outputStream = httpURLConnection.getOutputStream(); + + try { + outputStream.write(payload); + } finally { + outputStream.close(); + } + + int responseCode = httpURLConnection.getResponseCode(); + if (responseCode != 201) { + throw new RuntimeException("Expected HTTP_CREATED (201), obtained " + responseCode); + } + } + + private static HttpURLConnection connect(AnonymousFeedback.HttpConnectionFactory httpConnectionFactory, String url) throws IOException { + HttpURLConnection httpURLConnection = httpConnectionFactory.openHttpConnection(url); + httpURLConnection.setConnectTimeout(5000); + httpURLConnection.setReadTimeout(5000); + return httpURLConnection; + } + + public static class HttpConnectionFactory { + public HttpConnectionFactory() { + } + + protected HttpURLConnection openHttpConnection(String url) throws IOException { + return (HttpURLConnection) ((new URL(url)).openConnection()); + } + } +} diff --git a/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedbackTask.java b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedbackTask.java new file mode 100644 index 0000000000..7494643e1f --- /dev/null +++ b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedbackTask.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ro.redeul.google.go.diagnostics.error; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; +import com.intellij.util.net.HttpConfigurable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.LinkedHashMap; + +import static ro.redeul.google.go.diagnostics.error.AnonymousFeedback.sendFeedback; + +/** + * Sends crash reports to Github. + * Extensively inspired by the one used in the Android Studio. + * https://android.googlesource.com/platform/tools/adt/idea/+/master/android/src/com/android/tools/idea/diagnostics/error/ErrorReporter.java + * As per answer from here: http://devnet.jetbrains.com/message/5526206;jsessionid=F5422B4AF1AFD05AAF032636E5455E90#5526206 + */ +public class AnonymousFeedbackTask extends Task.Backgroundable { + private final Consumer myCallback; + private final Consumer myErrorCallback; + private final LinkedHashMap myParams; + + public AnonymousFeedbackTask(@Nullable Project project, + @NotNull String title, + boolean canBeCancelled, + LinkedHashMap params, + final Consumer callback, + final Consumer errorCallback) { + super(project, title, canBeCancelled); + + myParams = params; + myCallback = callback; + myErrorCallback = errorCallback; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); + try { + String token = sendFeedback(new ProxyHttpConnectionFactory(), myParams); + myCallback.consume(token); + } + catch (Exception e) { + myErrorCallback.consume(e); + } + } + + private static class ProxyHttpConnectionFactory extends AnonymousFeedback.HttpConnectionFactory { + @Override + protected HttpURLConnection openHttpConnection(String url) throws IOException { + return HttpConfigurable.getInstance().openHttpConnection(url); + } + } +} diff --git a/src/ro/redeul/google/go/diagnostics/error/ErrorReporter.java b/src/ro/redeul/google/go/diagnostics/error/ErrorReporter.java new file mode 100644 index 0000000000..390dc7b700 --- /dev/null +++ b/src/ro/redeul/google/go/diagnostics/error/ErrorReporter.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ro.redeul.google.go.diagnostics.error; + +import com.intellij.diagnostic.IdeErrorsDialog; +import com.intellij.diagnostic.LogMessageEx; +import com.intellij.diagnostic.ReportMessages; +import com.intellij.errorreport.bean.ErrorBean; +import com.intellij.ide.DataManager; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.idea.IdeaLogger; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.diagnostic.ErrorReportSubmitter; +import com.intellij.openapi.diagnostic.IdeaLoggingEvent; +import com.intellij.openapi.diagnostic.SubmittedReportInfo; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import ro.redeul.google.go.GoBundle; + +import java.awt.*; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Sends crash reports to Github. + * Extensively inspired by the one used in the Android Studio. + * https://android.googlesource.com/platform/tools/adt/idea/+/master/android/src/com/android/tools/idea/diagnostics/error/ErrorReporter.java + * As per answer from here: http://devnet.jetbrains.com/message/5526206;jsessionid=F5422B4AF1AFD05AAF032636E5455E90#5526206 + */ +public class ErrorReporter extends ErrorReportSubmitter { + @Override + public String getReportActionText() { + return GoBundle.message("go.error.report.action"); + } + + @Override + public boolean submit(@NotNull IdeaLoggingEvent[] events, String additionalInfo, @NotNull Component parentComponent, @NotNull Consumer consumer) { + ErrorBean errorBean = new ErrorBean(events[0].getThrowable(), IdeaLogger.ourLastActionId); + return doSubmit(events[0], parentComponent, consumer, errorBean, additionalInfo); + } + + private static boolean doSubmit(final IdeaLoggingEvent event, + final Component parentComponent, + final Consumer callback, + final ErrorBean bean, + final String description) { + final DataContext dataContext = DataManager.getInstance().getDataContext(parentComponent); + + bean.setDescription(description); + bean.setMessage(event.getMessage()); + + Throwable throwable = event.getThrowable(); + if (throwable != null) { + final PluginId pluginId = IdeErrorsDialog.findPluginId(throwable); + if (pluginId != null) { + final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(pluginId); + if (ideaPluginDescriptor != null && !ideaPluginDescriptor.isBundled()) { + bean.setPluginName(ideaPluginDescriptor.getName()); + bean.setPluginVersion(ideaPluginDescriptor.getVersion()); + } + } + } + + Object data = event.getData(); + + if (data instanceof LogMessageEx) { + bean.setAttachments(((LogMessageEx)data).getAttachments()); + } + + LinkedHashMap reportValues = IdeaITNProxy + .getKeyValuePairs(bean, + ApplicationManager.getApplication(), + (ApplicationInfoEx)ApplicationInfo.getInstance(), + ApplicationNamesInfo.getInstance()); + + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + + Consumer successCallback = new Consumer() { + @Override + public void consume(String token) { + final SubmittedReportInfo reportInfo = new SubmittedReportInfo( + null, "Issue " + token, SubmittedReportInfo.SubmissionStatus.NEW_ISSUE); + callback.consume(reportInfo); + + ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, + "Submitted", + NotificationType.INFORMATION, + null).setImportant(false).notify(project); + } + }; + + Consumer errorCallback = new Consumer() { + @Override + public void consume(Exception e) { + String message = GoBundle.message("go.error.report.message", e.getMessage()); + ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, + message, + NotificationType.ERROR, + NotificationListener.URL_OPENING_LISTENER).setImportant(false).notify(project); + } + }; + AnonymousFeedbackTask task = + new AnonymousFeedbackTask(project, "Submitting error report", true, reportValues, successCallback, errorCallback); + if (project == null) { + task.run(new EmptyProgressIndicator()); + } else { + ProgressManager.getInstance().run(task); + } + return true; + } +} diff --git a/src/ro/redeul/google/go/diagnostics/error/IdeaITNProxy.java b/src/ro/redeul/google/go/diagnostics/error/IdeaITNProxy.java new file mode 100644 index 0000000000..270dd623bd --- /dev/null +++ b/src/ro/redeul/google/go/diagnostics/error/IdeaITNProxy.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ro.redeul.google.go.diagnostics.error; + +import com.intellij.errorreport.bean.ErrorBean; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.diagnostic.Attachment; +import com.intellij.util.SystemProperties; + +import java.util.LinkedHashMap; + +/** + * Sends crash reports to Github. + * Extensively inspired by the one used in the Android Studio. + * https://android.googlesource.com/platform/tools/adt/idea/+/master/android/src/com/android/tools/idea/diagnostics/error/ErrorReporter.java + * As per answer from here: http://devnet.jetbrains.com/message/5526206;jsessionid=F5422B4AF1AFD05AAF032636E5455E90#5526206 + */ +public class IdeaITNProxy { + public static LinkedHashMap getKeyValuePairs(ErrorBean error, + Application application, + ApplicationInfoEx appInfo, + ApplicationNamesInfo namesInfo) { + LinkedHashMap params = new LinkedHashMap(21); + + params.put("error.description", error.getDescription()); + + params.put("Plugin Name", error.getPluginName()); + params.put("Plugin Version", error.getPluginVersion()); + + params.put("OS Name", SystemProperties.getOsName()); + params.put("Java version", SystemProperties.getJavaVersion()); + params.put("Java vm vendor", SystemProperties.getJavaVmVendor()); + + params.put("App Name", namesInfo.getProductName()); + params.put("App Full Name", namesInfo.getFullProductName()); + params.put("App Version name", appInfo.getVersionName()); + params.put("Is EAP", Boolean.toString(appInfo.isEAP())); + params.put("App Build", appInfo.getBuild().asString()); + params.put("App Version", appInfo.getFullVersion()); + + params.put("Last Action", error.getLastAction()); + + //params.put("previous.exception", error.getPreviousException() == null ? null : Integer.toString(error.getPreviousException())); + + params.put("error.message", error.getMessage()); + params.put("error.stacktrace", error.getStackTrace()); + + for (Attachment attachment : error.getAttachments()) { + params.put("attachment.name", attachment.getName()); + params.put("attachment.value", attachment.getEncodedBytes()); + } + + return params; + } +} From 99bcea72b69d4f0c66ed114eb3a7766d0ee489e0 Mon Sep 17 00:00:00 2001 From: Florin Patan Date: Sun, 2 Nov 2014 03:01:52 +0100 Subject: [PATCH 2/2] Use AppEngine proxy app to submit bugs to Github --- .../redeul/google/go/diagnostics/error/AnonymousFeedback.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java index 4b88d77d32..0a97fcea9c 100644 --- a/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java +++ b/src/ro/redeul/google/go/diagnostics/error/AnonymousFeedback.java @@ -72,7 +72,7 @@ private static String generateGithubIssueBody(LinkedHashMap body } private static void sendFeedback(AnonymousFeedback.HttpConnectionFactory httpConnectionFactory, byte[] payload) throws IOException { - String url = "https://api.github.com/repos/go-lang-plugin-org/go-lang-idea-plugin/issues"; + String url = "https://github-intellij-plugin.appspot.com/go-lang-plugin-org/go-lang-idea-plugin/submitError"; String userAgent = "golang IntelliJ IDEA plugin"; IdeaPluginDescriptorImpl pluginDescriptor = (IdeaPluginDescriptorImpl) PluginManager.getPlugin(PluginId.getId("ro.redeul.google.go")); @@ -87,7 +87,6 @@ private static void sendFeedback(AnonymousFeedback.HttpConnectionFactory httpCon httpURLConnection.setRequestMethod("POST"); httpURLConnection.setRequestProperty("User-Agent", userAgent); httpURLConnection.setRequestProperty("Content-Type", "application/json"); - httpURLConnection.setRequestProperty("Authorization", "token xxxx"); OutputStream outputStream = httpURLConnection.getOutputStream(); try {