Prevent constant reporting from released plugin versions in EA (KT-30388)
Apply a compromise strategy between "no report" and "always report". Report only for several days after release and don't bother users afterwards. #KT-30388 Fixed All exceptions still can be found in Idea Log.
This commit is contained in:
@@ -27,13 +27,18 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.io.HttpRequests
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import org.jetbrains.kotlin.idea.update.PluginUpdateVerifier
|
||||
import org.jdom.Attribute
|
||||
import org.jdom.DataConversionException
|
||||
import org.jetbrains.kotlin.idea.update.verify
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.net.URLEncoder
|
||||
import java.time.DateTimeException
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
sealed class PluginUpdateStatus {
|
||||
@@ -322,5 +327,46 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
||||
private val LOG = Logger.getInstance(KotlinPluginUpdater::class.java)
|
||||
|
||||
fun getInstance(): KotlinPluginUpdater = ServiceManager.getService(KotlinPluginUpdater::class.java)
|
||||
|
||||
class ResponseParseException(message: String, cause: Exception? = null) : IllegalStateException(message, cause)
|
||||
|
||||
@Throws(IOException::class, ResponseParseException::class)
|
||||
fun fetchPluginReleaseDate(pluginId: String, version: String): LocalDate? {
|
||||
// Need a better request (MP-2157)
|
||||
val url = "https://plugins.jetbrains.com/plugins/list?pluginId=$pluginId&pluginVersion=$version"
|
||||
|
||||
val responseDoc = HttpRequests.request(url).connect {
|
||||
JDOMUtil.load(it.inputStream)
|
||||
}
|
||||
|
||||
if (responseDoc.name != "plugin-repository") {
|
||||
throw ResponseParseException("No plugin repository element")
|
||||
}
|
||||
|
||||
val dateAttribute: Attribute = responseDoc
|
||||
.getChild("category")
|
||||
?.getChildren("idea-plugin")
|
||||
?.mapNotNull { pluginElement ->
|
||||
if (pluginElement.getChild("version")?.text == version) {
|
||||
pluginElement.getAttribute("date")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
?.singleOrNull()
|
||||
?: throw ResponseParseException("Can't find current plugin version")
|
||||
|
||||
val dateLong = try {
|
||||
dateAttribute.longValue
|
||||
} catch (e: DataConversionException) {
|
||||
throw ResponseParseException("Can't convert date value to long", e)
|
||||
}
|
||||
|
||||
return try {
|
||||
Instant.ofEpochMilli(dateLong).atZone(ZoneOffset.UTC).toLocalDate()
|
||||
} catch (e: DateTimeException) {
|
||||
throw ResponseParseException("Can't convert to date", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.intellij.openapi.updateSettings.impl.UpdateChecker;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.search.searches.IndexPatternSearch;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter;
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinTodoSearcher;
|
||||
import org.jetbrains.kotlin.utils.PathUtil;
|
||||
|
||||
@@ -82,6 +83,8 @@ public class PluginStartupComponent implements ApplicationComponent {
|
||||
|
||||
KotlinPluginCompatibilityVerifier.checkCompatibility();
|
||||
|
||||
KotlinReportSubmitter.Companion.setupReportingFromRelease();
|
||||
|
||||
//todo[Sedunov]: wait for fix in platform to avoid misunderstood from Java newbies (also ConfigureKotlinInTempDirTest)
|
||||
//KotlinSdkType.Companion.setUpIfNeeded();
|
||||
}
|
||||
|
||||
@@ -18,30 +18,204 @@ package org.jetbrains.kotlin.idea.reporter
|
||||
|
||||
import com.intellij.diagnostic.ReportMessages
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.SubmittedReportInfo
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.ThreeState
|
||||
import org.jetbrains.kotlin.idea.KotlinPluginUpdater
|
||||
import org.jetbrains.kotlin.idea.KotlinPluginUtil
|
||||
import org.jetbrains.kotlin.idea.PluginUpdateStatus
|
||||
import org.jetbrains.kotlin.idea.util.isEap
|
||||
import java.awt.Component
|
||||
import java.io.IOException
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.time.temporal.ChronoUnit
|
||||
import javax.swing.Icon
|
||||
|
||||
/**
|
||||
* We need to wrap ITNReporter for force showing or errors from kotlin plugin even from released version of IDEA.
|
||||
*/
|
||||
class KotlinReportSubmitter : ITNReporterCompat() {
|
||||
companion object {
|
||||
private const val KOTLIN_FATAL_ERROR_NOTIFICATION_PROPERTY = "kotlin.fatal.error.notification"
|
||||
private const val IDEA_FATAL_ERROR_NOTIFICATION_PROPERTY = "idea.fatal.error.notification"
|
||||
private const val DISABLED_VALUE = "disabled"
|
||||
private const val ENABLED_VALUE = "enabled"
|
||||
|
||||
private const val KOTLIN_PLUGIN_RELEASE_DATE = "kotlin.plugin.releaseDate"
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinReportSubmitter::class.java)
|
||||
|
||||
@Volatile
|
||||
private var isFatalErrorReportingDisabledInRelease = ThreeState.UNSURE
|
||||
|
||||
private val isIdeaAndKotlinRelease by lazy {
|
||||
// Disabled in released version of IDEA and Android Studio
|
||||
// Enabled in EAPs, Canary and Beta
|
||||
val isReleaseLikeIdea = DISABLED_VALUE == System.getProperty(IDEA_FATAL_ERROR_NOTIFICATION_PROPERTY, ENABLED_VALUE)
|
||||
|
||||
val isKotlinRelease =
|
||||
!(KotlinPluginUtil.isSnapshotVersion() || KotlinPluginUtil.isDevVersion() || isEap(KotlinPluginUtil.getPluginVersion()))
|
||||
|
||||
isReleaseLikeIdea && isKotlinRelease
|
||||
}
|
||||
|
||||
private const val NUMBER_OF_REPORTING_DAYS_FROM_RELEASE = 7
|
||||
|
||||
fun setupReportingFromRelease() {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isIdeaAndKotlinRelease) {
|
||||
return
|
||||
}
|
||||
|
||||
val currentPluginReleaseDate = readStoredPluginReleaseDate()
|
||||
if (currentPluginReleaseDate != null) {
|
||||
isFatalErrorReportingDisabledInRelease =
|
||||
if (isFatalErrorReportingDisabled(currentPluginReleaseDate)) ThreeState.YES else ThreeState.NO
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
val releaseDate =
|
||||
try {
|
||||
KotlinPluginUpdater.fetchPluginReleaseDate(
|
||||
KotlinPluginUtil.KOTLIN_PLUGIN_ID.idString,
|
||||
KotlinPluginUtil.getPluginVersion()
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
// Do not report connection problems
|
||||
null
|
||||
} catch (e: KotlinPluginUpdater.Companion.ResponseParseException) {
|
||||
// Exception won't be shown, but will be logged
|
||||
LOG.error(e)
|
||||
return@executeOnPooledThread
|
||||
}
|
||||
|
||||
if (releaseDate != null) {
|
||||
writePluginReleaseValue(releaseDate)
|
||||
} else {
|
||||
// Will try to fetch the same release date on IDE restart
|
||||
}
|
||||
|
||||
isFatalErrorReportingDisabledInRelease = isFatalErrorReportingWithDefault(releaseDate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFatalErrorReportingWithDefault(releaseDate: LocalDate?): ThreeState {
|
||||
return if (releaseDate != null) {
|
||||
if (isFatalErrorReportingDisabled(releaseDate)) ThreeState.YES else ThreeState.NO
|
||||
} else {
|
||||
// Disable reporting by default until we obtain a valid release date.
|
||||
// We might fail reporting exceptions that happened before initialization but after successful release date fetching
|
||||
// such exceptions maybe be reported after restart.
|
||||
ThreeState.YES
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFatalErrorReportingDisabledWithUpdate(): Boolean {
|
||||
val currentPluginReleaseDate = readStoredPluginReleaseDate()
|
||||
isFatalErrorReportingDisabledInRelease = isFatalErrorReportingWithDefault(currentPluginReleaseDate)
|
||||
|
||||
return isFatalErrorReportingDisabledInRelease == ThreeState.YES
|
||||
}
|
||||
|
||||
private fun isFatalErrorReportingDisabled(releaseDate: LocalDate): Boolean {
|
||||
return ChronoUnit.DAYS.between(releaseDate, LocalDate.now()) > NUMBER_OF_REPORTING_DAYS_FROM_RELEASE
|
||||
}
|
||||
|
||||
private val RELEASE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
|
||||
private fun readStoredPluginReleaseDate(): LocalDate? {
|
||||
val pluginVersionToReleaseDate = PropertiesComponent.getInstance().getValue(KOTLIN_PLUGIN_RELEASE_DATE) ?: return null
|
||||
|
||||
val parsedDate = fun(): LocalDate? {
|
||||
val parts = pluginVersionToReleaseDate.split(":")
|
||||
if (parts.size != 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
val pluginVersion = parts[0]
|
||||
if (pluginVersion != KotlinPluginUtil.getPluginVersion()) {
|
||||
// Stored for some other plugin version
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
val dateString = parts[1]
|
||||
LocalDate.parse(dateString, RELEASE_DATE_FORMATTER)
|
||||
} catch (e: DateTimeParseException) {
|
||||
null
|
||||
}
|
||||
}.invoke()
|
||||
|
||||
if (parsedDate == null) {
|
||||
PropertiesComponent.getInstance().setValue(KOTLIN_PLUGIN_RELEASE_DATE, null)
|
||||
}
|
||||
|
||||
return parsedDate
|
||||
}
|
||||
|
||||
private fun writePluginReleaseValue(date: LocalDate) {
|
||||
val currentKotlinVersion = KotlinPluginUtil.getPluginVersion()
|
||||
val dateStr = RELEASE_DATE_FORMATTER.format(date)
|
||||
PropertiesComponent.getInstance().setValue(KOTLIN_PLUGIN_RELEASE_DATE, "$currentKotlinVersion:$dateStr")
|
||||
}
|
||||
}
|
||||
|
||||
private var hasUpdate = false
|
||||
private var hasLatestVersion = false
|
||||
|
||||
override fun showErrorInRelease(event: IdeaLoggingEvent): Boolean {
|
||||
val notificationEnabled = "disabled" != System.getProperty("kotlin.fatal.error.notification", "enabled")
|
||||
return notificationEnabled && (!hasUpdate || ApplicationManager.getApplication().isInternal)
|
||||
if (ApplicationManager.getApplication().isInternal) {
|
||||
// Reporting is always enabled for internal mode in the platform
|
||||
return true
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (hasUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
val kotlinNotificationEnabled = DISABLED_VALUE != System.getProperty(KOTLIN_FATAL_ERROR_NOTIFICATION_PROPERTY, ENABLED_VALUE)
|
||||
if (!kotlinNotificationEnabled) {
|
||||
// Kotlin notifications are explicitly disabled
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isIdeaAndKotlinRelease) {
|
||||
return true
|
||||
}
|
||||
|
||||
return when (isFatalErrorReportingDisabledInRelease) {
|
||||
ThreeState.YES ->
|
||||
false
|
||||
|
||||
ThreeState.NO -> {
|
||||
// Reiterate the check for the case when there was no restart for long and reporting decision might expire
|
||||
!isFatalErrorReportingDisabledWithUpdate()
|
||||
}
|
||||
|
||||
ThreeState.UNSURE -> {
|
||||
// There might be an exception while initialization isn't complete.
|
||||
// Decide urgently based on previously stored release version if already fetched.
|
||||
!isFatalErrorReportingDisabledWithUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun submitCompat(
|
||||
@@ -54,6 +228,8 @@ class KotlinReportSubmitter : ITNReporterCompat() {
|
||||
if (ApplicationManager.getApplication().isInternal) {
|
||||
return super.submitCompat(events, additionalInfo, parentComponent, consumer)
|
||||
}
|
||||
|
||||
// TODO: What happens here? User clicks report but no report is send?
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -68,7 +244,8 @@ class KotlinReportSubmitter : ITNReporterCompat() {
|
||||
ReportMessages.ERROR_REPORT,
|
||||
"Can't report exception from patched plugin",
|
||||
NotificationType.INFORMATION,
|
||||
null)
|
||||
null
|
||||
)
|
||||
.setImportant(false)
|
||||
.notify(project)
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user