diff --git a/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java b/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java index 72e5284666bc..16725a6022cf 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java @@ -28,6 +28,7 @@ public abstract class AbstractMessage { private SubmittedReportInfo mySubmissionInfo; private String myAdditionalInfo; private Notification myNotification; + private Integer myAssigneeId; private final Date myDate; @@ -90,4 +91,11 @@ public abstract class AbstractMessage { return myDate; } + public Integer getAssigneeId() { + return myAssigneeId; + } + + public void setAssigneeId(Integer assigneeId) { + myAssigneeId = assigneeId; + } } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/Developer.java b/platform/platform-impl/src/com/intellij/diagnostic/Developer.java new file mode 100644 index 000000000000..cc1936aad2c5 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/Developer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2012 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 com.intellij.diagnostic; + +public class Developer { + public static final Developer NULL = new NullDeveloper(); + + private Integer myId; + + private String myName; + + private Developer() { + } + + public Developer(int id, String name) { + myId = id; + myName = name; + } + + public Integer getId() { + return myId; + } + + public String getDisplayText() { + return myName; + } + + public String getSearchableText() { + return myName; + } + + @Override + public String toString() { + return String.format("%d - %s", myId, myName); + } + + private static class NullDeveloper extends Developer { + @Override + public Integer getId() { + return null; + } + + @Override + public String getDisplayText() { + return ""; + } + + @Override + public String getSearchableText() { + return ""; + } + + @Override + public String toString() { + return "NullDeveloper"; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java b/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java new file mode 100644 index 000000000000..cd9d3b53416d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2012 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 com.intellij.diagnostic; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +class DevelopersLoader { + private static final String DEVELOPERS_LIST_URL = "http://ea-engine.labs.intellij.net/data?category=developers"; + private static final String DATA_CHARSET = "utf-8"; + + private DevelopersLoader() { + } + + public static Collection fetchDevelopers() throws IOException { + try { + List developers = new LinkedList(); + developers.add(Developer.NULL); + + URL url = new URL(DEVELOPERS_LIST_URL); + BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), DATA_CHARSET)); + + try { + while (reader.ready()) { + String line = reader.readLine(); + if (line == null) break; + int i = line.indexOf('\t'); + if (i == -1) throw new IOException("Protocol error"); + int id = Integer.parseInt(line.substring(0, i)); + String name = line.substring(i + 1); + developers.add(new Developer(id, name)); + } + return developers; + } finally { + reader.close(); + } + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java index 63ad603da4aa..7293f36dff5e 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java @@ -121,8 +121,14 @@ public class ITNReporter extends ErrorReportSubmitter { } } - if (event.getData() instanceof LogMessageEx) { - errorBean.setAttachments(((LogMessageEx)event.getData()).getAttachments()); + Object data = event.getData(); + + if (data instanceof AbstractMessage) { + errorBean.setAssigneeId(((AbstractMessage)data).getAssigneeId()); + } + + if (data instanceof LogMessageEx) { + errorBean.setAttachments(((LogMessageEx)data).getAttachments()); } @NonNls String login = errorReportConfigurable.ITN_LOGIN; diff --git a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java index aa2ecac599d8..055b9d64427d 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java @@ -8,6 +8,10 @@ import com.intellij.ide.impl.DataManagerImpl; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManager; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationDisplayType; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; @@ -22,6 +26,9 @@ import com.intellij.openapi.extensions.ExtensionException; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -42,15 +49,15 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; +import javax.swing.event.*; import java.awt.*; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -63,6 +70,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene @NonNls private static final String ACTIVE_TAB_OPTION = IdeErrorsDialog.class.getName() + "activeTab"; public static DataKey CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key"); public static final int COMPONENTS_WIDTH = 670; + public static Collection ourDevelopersList = Collections.emptyList(); private JPanel myContentPane; private JPanel myBackButtonPanel; @@ -105,6 +113,38 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } setCancelButtonText(CommonBundle.message("close.action.name")); setModal(false); + if (INTERNAL_MODE) { + if (ourDevelopersList.isEmpty()) { + loadDevelopersAsynchronously(); + } else { + myDetailsTabForm.setDevelopers(ourDevelopersList); + } + } + } + + private void loadDevelopersAsynchronously() { + Task.Backgroundable task = new Task.Backgroundable(null, "Loading developers list", true) { + private final Collection[] myDevelopers = new Collection[]{Collections.emptyList()}; + + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + myDevelopers[0] = DevelopersLoader.fetchDevelopers(); + } catch (IOException e) { + Notifications.Bus.register("Error reporter", NotificationDisplayType.BALLOON); + Notifications.Bus.notify(new Notification("Error reporter", "Communication error", + "Unable to load developers list from server.", NotificationType.WARNING)); + } + } + + @Override + public void onSuccess() { + Collection developers = myDevelopers[0]; + myDetailsTabForm.setDevelopers(developers); + ourDevelopersList = developers; + } + }; + ProgressManager.getInstance().run(task); } private boolean moveSelectionToMessage(LogMessage defaultMessage) { @@ -304,6 +344,18 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } }); + myDetailsTabForm.addAssigneeListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (myMute) return; + + AbstractMessage message = getSelectedMessage(); + if (message != null) { + message.setAssigneeId(myDetailsTabForm.getAssigneeId()); + } + } + }); + return myContentPane; } @@ -377,6 +429,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene final AbstractMessage message = getSelectedMessage(); updateInfoLabel(message); updateCredentialsPane(message); + updateAssigneePane(message); updateAttachmentWarning(message); myDisableLink.setVisible(canDisablePlugin(message)); updateForeignPluginLabel(message != null ? message : null); @@ -457,6 +510,11 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene myCredentialsPanel.setVisible(false); } + private void updateAssigneePane(AbstractMessage message) { + final ErrorReportSubmitter submitter = getSubmitter(message.getThrowable()); + myDetailsTabForm.setAssigneeVisible(submitter instanceof ITNReporter && INTERNAL_MODE); + } + private void updateInfoLabel(AbstractMessage message) { if (message == null) { myInfoLabel.setText(""); @@ -632,6 +690,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene myDetailsTabForm.setCommentsTextEnabled(false); } + myDetailsTabForm.setAssigneeId(message == null ? null : message.getAssigneeId()); + List attachments = message instanceof LogMessageEx ? ((LogMessageEx)message).getAttachments() : Collections.emptyList(); if (!attachments.isEmpty()) { @@ -891,7 +951,12 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene if (logMessage instanceof LogMessageEx) { return ((LogMessageEx)logMessage).toEvent(); } - return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable()); + return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable()) { + @Override + public AbstractMessage getData() { + return logMessage; + } + }; } } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.form b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.form index 54115c0d13c9..754c52dc5678 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.form +++ b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.form @@ -1,6 +1,6 @@
- + @@ -52,6 +52,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.java b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.java index feb3b62d53ac..39cc7203aef7 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DetailsTabForm.java @@ -1,13 +1,19 @@ package com.intellij.diagnostic.errordialog; +import com.intellij.diagnostic.Developer; import com.intellij.diagnostic.DiagnosticBundle; import com.intellij.diagnostic.IdeErrorsDialog; +import com.intellij.ui.ComboboxSpeedSearch; import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.containers.ComparatorUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; /** * @author ksafonov @@ -18,6 +24,10 @@ public class DetailsTabForm { private LabeledTextComponent myCommentsArea; private JPanel myDetailsHolder; private JButton myAnalyzeStacktraceButton; + private JComboBox myAssigneeComboBox; + private JPanel myAssigneePanel; + private Integer myAssigneeId; + private boolean myProcessEvents = true; public DetailsTabForm(@Nullable Action analyzeAction) { myCommentsArea.setTitle(DiagnosticBundle.message("error.dialog.comment.prompt")); @@ -30,6 +40,20 @@ public class DetailsTabForm { else { myAnalyzeStacktraceButton.setVisible(false); } + myAssigneeComboBox.setRenderer(new DeveloperRenderer(myAssigneeComboBox.getRenderer())); + myAssigneeComboBox.setPrototypeDisplayValue(new Developer(0, "Here Goes Some Very Long String")); + myAssigneeComboBox.addActionListener(new ActionListenerProxy(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myAssigneeId = getAssigneeId(); + } + })); + new ComboboxSpeedSearch(myAssigneeComboBox) { + @Override + protected String getElementText(Object element) { + return element == null ? "" : ((Developer) element).getSearchableText(); + } + }; } public void setCommentsAreaVisible(boolean b) { @@ -65,4 +89,68 @@ public class DetailsTabForm { myCommentsArea.addCommentsListener(l); } + public void setAssigneeVisible(boolean visible) { + myAssigneePanel.setVisible(visible); + } + + public void setDevelopers(Collection developers) { + myAssigneeComboBox.setModel(new DefaultComboBoxModel(developers.toArray())); + updateSelectedDeveloper(); + } + + public void setAssigneeId(@Nullable Integer assigneeId) { + myAssigneeId = assigneeId; + if (myAssigneeComboBox.getItemCount() > 0) { + updateSelectedDeveloper(); + } + } + + private void updateSelectedDeveloper() { + myProcessEvents = false; + + Integer index = null; + for (int i = 0; i < myAssigneeComboBox.getItemCount(); i++) { + Developer developer = (Developer) myAssigneeComboBox.getItemAt(i); + if (ComparatorUtil.equalsNullable(developer.getId(), myAssigneeId)) { + index = i; + break; + } + } + setSelectedAssigneeIndex(index); + + myProcessEvents = true; + } + + private void setSelectedAssigneeIndex(Integer index) { + if (index == null) { + myAssigneeComboBox.setSelectedItem(null); + } else { + myAssigneeComboBox.setSelectedIndex(index); + } + } + + @Nullable + public Integer getAssigneeId() { + Developer assignee = (Developer) myAssigneeComboBox.getSelectedItem(); + return assignee == null ? null : assignee.getId(); + } + + public void addAssigneeListener(ActionListener listener) { + myAssigneeComboBox.addActionListener(new ActionListenerProxy(listener)); + } + + private class ActionListenerProxy implements ActionListener { + private final ActionListener myDelegate; + + public ActionListenerProxy(ActionListener delegate) { + myDelegate = delegate; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (myProcessEvents) { + myDelegate.actionPerformed(e); + } + } + } } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DeveloperRenderer.java b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DeveloperRenderer.java new file mode 100644 index 000000000000..7602bf413725 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/errordialog/DeveloperRenderer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2012 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 com.intellij.diagnostic.errordialog; + +import com.intellij.diagnostic.Developer; +import com.intellij.ide.ui.ListCellRendererWrapper; + +import javax.swing.*; + +class DeveloperRenderer extends ListCellRendererWrapper { + DeveloperRenderer(ListCellRenderer listCellRenderer) { + super(listCellRenderer); + } + + @Override + public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + setText(value == null ? "" : ((Developer) value).getDisplayText()); + } +} diff --git a/platform/platform-impl/src/com/intellij/errorreport/bean/ErrorBean.java b/platform/platform-impl/src/com/intellij/errorreport/bean/ErrorBean.java index 6af88334011d..e68cd53c5e46 100644 --- a/platform/platform-impl/src/com/intellij/errorreport/bean/ErrorBean.java +++ b/platform/platform-impl/src/com/intellij/errorreport/bean/ErrorBean.java @@ -38,6 +38,7 @@ public class ErrorBean { private String message; private String stackTrace; private String description; + private Integer assigneeId; private List attachments = Collections.emptyList(); @@ -107,4 +108,12 @@ public class ErrorBean { public List getAttachments() { return attachments; } + + public Integer getAssigneeId() { + return assigneeId; + } + + public void setAssigneeId(Integer assigneeId) { + this.assigneeId = assigneeId; + } } diff --git a/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java b/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java index 4de169090788..54b067212dc4 100644 --- a/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java +++ b/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java @@ -144,6 +144,8 @@ public class ITNProxy { params.add(Pair.create("error.description", error.getDescription())); + params.add(Pair.create("assignee.id", error.getAssigneeId() == null ? null : Integer.toString(error.getAssigneeId()))); + for (Attachment attachment : error.getAttachments()) { params.add(Pair.create("attachment.name", attachment.getName())); params.add(Pair.create("attachment.value", attachment.getEncodedBytes()));