diff --git a/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java b/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java index e5787a21bc62..a3d2621ab6eb 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/AbstractMessage.java @@ -20,6 +20,8 @@ public abstract class AbstractMessage { private SubmittedReportInfo mySubmissionInfo; private String myAdditionalInfo; private Integer myAssigneeId; + private boolean myAssigneeVisible; + private Long myDevelopersTimestamp; public abstract @NotNull Throwable getThrowable(); public abstract @NotNull String getThrowableText(); @@ -96,6 +98,22 @@ public abstract class AbstractMessage { myAssigneeId = assigneeId; } + boolean isAssigneeVisible() { + return myAssigneeVisible; + } + + void setAssigneeVisible(boolean assigneeVisible) { + myAssigneeVisible = assigneeVisible; + } + + public @Nullable Long getDevelopersTimestamp() { + return myDevelopersTimestamp; + } + + public void setDevelopersTimestamp(@Nullable Long developersTimestamp) { + myDevelopersTimestamp = developersTimestamp; + } + /** @deprecated use {@link #getIncludedAttachments()} instead (to be removed in IDEA 2020) */ @Deprecated public List getAttachments() { diff --git a/platform/platform-impl/src/com/intellij/diagnostic/Developer.java b/platform/platform-impl/src/com/intellij/diagnostic/Developer.java index 8764754a47b5..123b5c8b6be1 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/Developer.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/Developer.java @@ -1,14 +1,25 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.diagnostic; +import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.util.xmlb.annotations.Tag; import org.jetbrains.annotations.Nullable; +@Tag("developer") public class Developer { public static final Developer NULL = new Developer(-1, ""); + @Attribute("id") private final int myId; + + @Attribute("name") private final String myName; + @SuppressWarnings("unused") // need for xml serialization + private Developer() { + this(0, ""); + } + public Developer(int id, String name) { myId = id; myName = name; diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.kt b/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.kt index c84b5ddc83cb..2b4885c66e7d 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.kt @@ -5,33 +5,69 @@ import com.intellij.credentialStore.CredentialAttributes import com.intellij.credentialStore.Credentials import com.intellij.credentialStore.SERVICE_NAME_PREFIX import com.intellij.ide.passwordSafe.PasswordSafe -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.RoamingType -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.* import com.intellij.util.io.decodeBase64 +import com.intellij.util.xmlb.XmlSerializer +import org.jdom.Element -@State(name = "ErrorReportConfigurable", storages = [(Storage(value = "other.xml", deprecated = true, roamingType = RoamingType.DISABLED))]) -internal class ErrorReportConfigurable : PersistentStateComponent { +@State(name = "ErrorReportConfigurable", storages = [Storage(value = "other.xml", deprecated = true, roamingType = RoamingType.DISABLED), Storage(value = "errorReporting.xml")]) +internal class ErrorReportConfigurable : PersistentStateComponent { companion object { + @JvmStatic val SERVICE_NAME = "$SERVICE_NAME_PREFIX — JetBrains Account" + @JvmStatic + val instance: ErrorReportConfigurable + get() = ServiceManager.getService(ErrorReportConfigurable::class.java) + @JvmStatic fun getCredentials() = PasswordSafe.instance.get(CredentialAttributes(SERVICE_NAME)) } - override fun getState() = OldState() + private var myState: State? = null + + var developer: Developers? + get() = myState?.let { Developers(it.developers.toList(), it.timestamp) } + set(value) { + myState = value?.let { State(it.developers.toList(), it.timestamp) } + } + + override fun getState(): Element? { + return myState?.let { XmlSerializer.serialize(it) } + } + + override fun loadState(element: Element) { + loadOldState(element) + myState = XmlSerializer.deserialize(element, State::class.java) + } + + private fun loadOldState(element: Element) { + val state = XmlSerializer.deserialize(element, OldState::class.java) - override fun loadState(state: OldState) { if (!state.ITN_LOGIN.isNullOrEmpty() || !state.ITN_PASSWORD_CRYPT.isNullOrEmpty()) { PasswordSafe.instance.set(CredentialAttributes(SERVICE_NAME, state.ITN_LOGIN), Credentials(state.ITN_LOGIN, state.ITN_PASSWORD_CRYPT!!.decodeBase64())) } } + + private data class State(var developers: List, var timestamp: Long) { + @Suppress("unused") + private constructor(): this(emptyList(), 0) // need for xml serialization + } + + @Suppress("PropertyName") + private class OldState { + var ITN_LOGIN: String? = null + var ITN_PASSWORD_CRYPT: String? = null + } } -@Suppress("PropertyName") -internal class OldState { - var ITN_LOGIN: String? = null - var ITN_PASSWORD_CRYPT: String? = null +internal data class Developers(val developers: List, val timestamp: Long) { + companion object { + private const val UPDATE_INTERVAL = 24L * 60 * 60 * 1000 // 24 hours + } + + fun isUpToDateAt(timestamp: Long): Boolean { + return (timestamp - this.timestamp < UPDATE_INTERVAL) && developers.isNotEmpty() + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java index 1e75598e5c59..6bdd35481016 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java @@ -61,7 +61,7 @@ class ITNProxy { private static final NotNullLazyValue> TEMPLATE = AtomicNotNullLazyValue.createValue(() -> { Map template = new LinkedHashMap<>(); - template.put("protocol.version", "1"); + template.put("protocol.version", "1.1"); template.put("os.name", SystemInfo.OS_NAME); template.put("java.version", SystemInfo.JAVA_VERSION); template.put("java.vm.vendor", SystemInfo.JAVA_VENDOR); @@ -254,6 +254,10 @@ class ITNProxy { if (messageObj.getAssigneeId() != null) { append(builder, "assignee.id", Integer.toString(messageObj.getAssigneeId())); } + append(builder, "assignee.list.visible", Boolean.toString(messageObj.isAssigneeVisible())); + if (messageObj.getDevelopersTimestamp() != null) { + append(builder, "assignee.list.timestamp", Long.toString(messageObj.getDevelopersTimestamp())); + } } return builder.toString().getBytes(StandardCharsets.UTF_8); diff --git a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java index 4555a52bf9c3..d3d77ff1fd97 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java @@ -30,7 +30,6 @@ import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; @@ -74,15 +73,15 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene private static final String ACCEPTED_NOTICES_KEY = "exception.accepted.notices"; private static final String ACCEPTED_NOTICES_SEPARATOR = ":"; private static final String DISABLE_PLUGIN_URL = "#disable"; - - private static List ourDevelopersList = Collections.emptyList(); + private static final String EA_PLUGIN_ID = "com.intellij.sisyphus"; private final MessagePool myMessagePool; private final Project myProject; - private final boolean myInternalMode; + private final boolean myAssigneeVisible; private final Set myAcceptedNotices; private final List myMessageClusters = new ArrayList<>(); // exceptions with the same stacktrace private int myIndex, myLastIndex = -1; + private Long myDevelopersTimestamp; private JLabel myCountLabel; private JTextComponent myInfoLabel; @@ -101,14 +100,14 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene super(project, true); myMessagePool = messagePool; myProject = project; - myInternalMode = ApplicationManager.getApplication().isInternal(); + myAssigneeVisible = ApplicationManager.getApplication().isInternal() || PluginManager.isPluginInstalled(PluginId.getId(EA_PLUGIN_ID)); setTitle(DiagnosticBundle.message("error.list.title")); setModal(false); init(); setCancelButtonText(CommonBundle.message("close.action.name")); - if (myInternalMode) { + if (myAssigneeVisible) { loadDevelopersList(); } @@ -123,30 +122,45 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } private void loadDevelopersList() { - if (!ourDevelopersList.isEmpty()) { - myAssigneeCombo.setModel(new CollectionComboBoxModel<>(ourDevelopersList)); + ErrorReportConfigurable configurable = ErrorReportConfigurable.getInstance(); + Developers developers = configurable.getDeveloper(); + if (developers != null && developers.isUpToDateAt(System.currentTimeMillis())) { + setDevelopers(developers); } else { new Task.Backgroundable(null, "Loading Developers List", true) { @Override public void run(@NotNull ProgressIndicator indicator) { try { - List developers = ITNProxy.fetchDevelopers(indicator); - //noinspection AssignmentToStaticFieldFromInstanceMethod - ourDevelopersList = developers; + Developers updatedDevelopers = new Developers(ITNProxy.fetchDevelopers(indicator), System.currentTimeMillis()); UIUtil.invokeLaterIfNeeded(() -> { + configurable.setDeveloper(updatedDevelopers); if (isShowing()) { - myAssigneeCombo.setModel(new CollectionComboBoxModel<>(developers)); + setDevelopers(updatedDevelopers); + } + }); + } + catch (UnknownHostException e) { + LOG.debug(e); + UIUtil.invokeLaterIfNeeded(() -> { + if (isShowing()) { + setDevelopers(developers); } }); } - catch (UnknownHostException e) { LOG.debug(e); } catch (IOException e) { LOG.warn(e); } } }.queue(); } } + private void setDevelopers(@Nullable Developers developers) { + if (developers != null) { + myAssigneeCombo.setModel(new CollectionComboBoxModel<>(developers.getDevelopers())); + myDevelopersTimestamp = developers.getTimestamp(); + } + } + private int selectMessage(@Nullable LogMessage defaultMessage) { if (defaultMessage != null) { for (int i = 0; i < myMessageClusters.size(); i++) { @@ -255,7 +269,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } }); - if (myInternalMode) { + if (myAssigneeVisible) { myAssigneeCombo = new ComboBox<>(); myAssigneeCombo.setRenderer(new ListCellRendererWrapper() { @Override @@ -312,7 +326,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene attachmentsPanel.add(scrollPane(myAttachmentArea, 500, 350), BorderLayout.CENTER); JPanel accountRow = new JPanel(new BorderLayout()); - if (myInternalMode) accountRow.add(myAssigneePanel, BorderLayout.WEST); + if (myAssigneeVisible) accountRow.add(myAssigneePanel, BorderLayout.WEST); accountRow.add(myCredentialsLabel, BorderLayout.EAST); myNoticePanel = new JPanel(new GridBagLayout()); myNoticePanel.add(new JBLabel(UIUtil.getBalloonWarningIcon()), new GridBagConstraints(0, 0, 1, 1, 0, 0, NORTH, NONE, JBUI.insets(7, 0, 0, 5), 0, 0)); @@ -341,7 +355,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene @Override protected Action[] createActions() { List actions = new ArrayList<>(); - if (myInternalMode && myProject != null && !myProject.isDefault()) { + if (myAssigneeVisible && myProject != null && !myProject.isDefault()) { AnAction action = ActionManager.getInstance().getAction("Unscramble"); if (action != null) { actions.add(new AnalyzeAction(action)); @@ -411,7 +425,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene updateLabels(cluster); updateDetails(cluster); - if (myInternalMode) { + if (myAssigneeVisible) { updateAssigneePanel(cluster); } updateCredentialsPanel(submitter); @@ -541,8 +555,13 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene myAssigneeCombo.setSelectedIndex(-1); } else { - Condition lookup = d -> Objects.equals(assignee, d.getId()); - myAssigneeCombo.setSelectedIndex(ContainerUtil.indexOf(ourDevelopersList, lookup)); + int assigneeIndex = getAssigneeIndex(assignee); + if (assigneeIndex != -1) { + myAssigneeCombo.setSelectedIndex(assigneeIndex); + } + else { + cluster.first.setAssigneeId(null); + } } } else { @@ -550,6 +569,16 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } } + private int getAssigneeIndex(Integer assigneeId) { + for (int index = 0; index < myAssigneeCombo.getItemCount(); index++) { + if (Objects.equals(assigneeId, myAssigneeCombo.getItemAt(index).getId())) { + return index; + } + } + + return -1; + } + private void updateCredentialsPanel(ErrorReportSubmitter submitter) { if (submitter instanceof ITNReporter) { myCredentialsLabel.setVisible(true); @@ -571,6 +600,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene if (submitter == null) return false; AbstractMessage message = cluster.first; + message.setAssigneeVisible(myAssigneeVisible); + message.setDevelopersTimestamp(myDevelopersTimestamp); message.setSubmitting(true); String notice = submitter.getPrivacyNoticeText();