package org.elixir_lang.errorreport;
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.diagnostic.Attachment;
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.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
* @see <a href="https://github.com/JesusFreke/smali/blob/87d10dac2773cd35b7d5825d7957206e26c1727b/smalidea/src/main/java/org/jf/smalidea/errorReporting/ErrorReporter.java">{@code org.jf.smalidea.errorReporting.ErrorReporter}</a>
*/
public class Submitter extends com.intellij.openapi.diagnostic.ErrorReportSubmitter {
/*
* CONSTANTS
*/
private static final String ORGANIZATION = "KronicDeth";
private static final String REPOSITORY = "intellij-elixir";
private static final String REPOSITORY_URL = "https://github.com/" + ORGANIZATION + "/" + REPOSITORY;
static final String ISSUES_URL = REPOSITORY_URL + "/issues";
/*
* Static Methods
*/
@NotNull
private static Consumer<Exception> errorCallback(@Nullable final Project project) {
return new Consumer<Exception>() {
@Override
public void consume(Exception e) {
String message = String.format(
"<html>\n" +
" There was an error while creating a GitHub issue: %s\n" +
" <br>\n" +
" Please consider <a href=\"" + ISSUES_URL +
"/new\">manually creating an issue</a>\n" +
"</html>",
e.getMessage()
);
ReportMessages.GROUP.createNotification(
ReportMessages.ERROR_REPORT,
message,
NotificationType.ERROR,
NotificationListener.URL_OPENING_LISTENER
).setImportant(false).notify(project);
}
};
}
@NotNull
private static ErrorBean errorBean(@NotNull IdeaLoggingEvent[] events, String additionalInfo) {
IdeaLoggingEvent event = events[0];
ErrorBean bean = new ErrorBean(event.getThrowable(), IdeaLogger.ourLastActionId);
bean.setDescription(additionalInfo);
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(includedAttachments((LogMessageEx) data));
}
return bean;
}
/**
* Gets the attachment the user has marked to be included. Tries to use
* {@link LogMessageEx#getIncludedAttachments()}, and then {@link LogMessageEx#getAttachments()}.
*
* @param logMessageEx with attachments
* @return the attachments
*/
private static List<Attachment> includedAttachments(LogMessageEx logMessageEx) {
Class<LogMessageEx> klass = LogMessageEx.class;
List<Attachment> attachmentList = Collections.emptyList();
try {
Method getIncludedAttachments = klass.getDeclaredMethod("getIncludedAttachments", LogMessageEx.class);
try {
attachmentList = (List<Attachment>) getIncludedAttachments.invoke(logMessageEx);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException noSuchGetIncludedAttachmentsMethod) {
try {
Method getAttachments = klass.getDeclaredMethod("getAttachments", LogMessageEx.class);
try {
attachmentList = (List<Attachment>) getAttachments.invoke(logMessageEx);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException noSuchGetAttachmentsMethod) {
noSuchGetAttachmentsMethod.printStackTrace();
}
}
return attachmentList;
}
@Nullable
private static Project project(@NotNull Component parentComponent) {
DataContext dataContext = DataManager.getInstance().getDataContext(parentComponent);
return CommonDataKeys.PROJECT.getData(dataContext);
}
/**
* Runs the task in the background if it can.
*
* @param task Task that will create an issue on Github
* @param direct {@code true} to run directly with {@link Task#run(ProgressIndicator)}; {@code false} to run with
* the {@link com.intellij.openapi.project.ProjectManager}.
*/
private static void run(Task task, boolean direct) {
if (direct) {
task.run(new EmptyProgressIndicator());
} else {
ProgressManager.getInstance().run(task);
}
}
@NotNull
private static Consumer<Boolean> successCallback(
@Nullable final Project project,
@NotNull final Consumer<SubmittedReportInfo> consumer
) {
return new Consumer<Boolean>() {
@Override
public void consume(Boolean opened) {
String url = null;
String linkText = null;
//noinspection ConstantConditions
final SubmittedReportInfo reportInfo = new SubmittedReportInfo(
url,
linkText,
SubmittedReportInfo.SubmissionStatus.NEW_ISSUE
);
consumer.consume(reportInfo);
// pseudo-named-arguments
NotificationListener notificationListener = null;
//noinspection ConstantConditions
ReportMessages.GROUP.createNotification(
ReportMessages.ERROR_REPORT,
"Submitted",
NotificationType.INFORMATION,
notificationListener
).setImportant(false).notify(project);
}
};
}
/*
*
* Instance Methods
*
*/
/*
* Public Instance Methods
*/
/**
* @return an action text to be used in Error Reporter user interface, e.g. "Report to JetBrains".
*/
@Override
public String getReportActionText() {
return "Open issue against " + REPOSITORY_URL;
}
/**
* This method is called whenever an exception in a plugin code had happened and a user decided to report a problem
* to the plugin vendor.
*
* @param events a non-empty sequence of error descriptors.
* @param additionalInfo additional information provided by a user.
* @param parentComponent UI component to use as a parent in any UI activity from a submitter.
* @param consumer a callback to be called after sending is finished (or failed).
* @return {@code true} if reporting was started, {@code false} if a report can't be sent at the moment.
*/
@Override
public boolean submit(@NotNull IdeaLoggingEvent[] events,
String additionalInfo,
@NotNull Component parentComponent,
@NotNull final Consumer<SubmittedReportInfo> consumer) {
final Project project = project(parentComponent);
ErrorBean errorBean = errorBean(events, additionalInfo);
Consumer<Boolean> successCallback = successCallback(project, consumer);
Consumer<Exception> errorCallback = errorCallback(project);
Task task = new Task(project, errorBean, successCallback, errorCallback);
run(task, project == null);
return true;
}
}