Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
- Fix too much notifications for Vue with JS
- Cache config
- Better logs for RequestSender
- Port crash reporter improvements from NUD, fixing crash
- Upgrade dependencies
  • Loading branch information
WarningImHack3r committed May 18, 2024
1 parent bb51d83 commit e7ca900
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 61 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
- Consequently, the plugin now depends
on [Svelte](https://plugins.jetbrains.com/plugin/12375-svelte) & [Vue](https://plugins.jetbrains.com/plugin/9442-vue-js)
extensions and only works on WebStorm or IntelliJ IDEA Ultimate
- Improve crash reporter to include more relevant information

### Fixed

- Fix a potential crash with 2024.1+ IDEs
- Fix JS users with Vue getting notified too often about the unavailability of the JS option

## [0.7.7] - 2024-03-29

Expand Down
10 changes: 6 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ changelog {
}

// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration
koverReport {
defaults {
xml {
onCheck = true
kover {
reports {
total {
xml {
onCheck = true
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[versions]
kotlin = "1.9.23"
kotlin = "1.9.24"
changelog = "2.2.0"
gradleIntelliJPlugin = "1.17.3"
qodana = "2024.1.3"
kover = "0.7.6"
qodana = "2024.1.5"
kover = "0.8.0"

[plugins]
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,24 @@ object RequestSender {
log.debug(
"Request method: ${request.method()}, headers: ${
request.headers().map()
}, body: ${body?.take(100)}${if ((body?.length ?: 0) > 100) "..." else ""}"
}, body:${
if (body != null) {
"\n"
} else ""
}${body?.take(100)}${if ((body?.length ?: 0) > 100) "..." else ""}"
)
HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build()
.send(request, HttpResponse.BodyHandlers.ofString()).let { response ->
return Response(response.statusCode(), response.headers().map(), response.body()).also {
log.debug("Request to $url returned ${it.statusCode} (${it.body.length} bytes): ${it.body.take(100)}${if (it.body.length > 100) "..." else ""}")
log.debug(
"Request to $url returned ${it.statusCode} (${it.body.length} bytes):${
if (it.body.isNotEmpty()) {
"\n"
} else ""
}${it.body.take(100)}${if (it.body.length > 100) "..." else ""}"
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.nio.file.NoSuchFileException
abstract class Source<C : Config>(val project: Project, private val serializer: KSerializer<C>) {
private val log = logger<Source<C>>()
abstract var framework: String
private var config: C? = null
protected val domain: String
get() = URI(getLocalConfig().`$schema`).let { uri ->
"${uri.scheme}://${uri.host}".also {
Expand All @@ -34,15 +35,22 @@ abstract class Source<C : Config>(val project: Project, private val serializer:
// Utility methods
protected fun getLocalConfig(): C {
val file = "components.json"
return FileManager(project).getFileContentsAtPath(file)?.let {
return config?.also {
log.debug("Returning cached config")
} ?: FileManager(project).getFileContentsAtPath(file)?.let {
log.debug("Parsing config from $file")
try {
Json.decodeFromString(serializer, it).also { config ->
log.debug("Parsed config: ${config.javaClass.name}")
Json.decodeFromString(serializer, it).also {
log.debug("Parsed config")
}
} catch (e: Exception) {
throw UnparseableConfigException(project, "Unable to parse $file", e)
}
}?.also {
if (config == null) {
log.debug("Caching config")
config = it
}
} ?: throw NoSuchFileException("$file not found")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import java.nio.file.NoSuchFileException
class VueSource(project: Project) : Source<VueConfig>(project, VueConfig.serializer()) {
companion object {
private val log = logger<VueSource>()
private var jsVueNotified = false
}

override var framework = "Vue"
Expand Down Expand Up @@ -74,11 +75,14 @@ class VueSource(project: Project) : Source<VueConfig>(project, VueConfig.seriali
override fun adaptFileToConfig(file: PsiFile) {
val config = getLocalConfig()
if (!config.typescript) {
NotificationManager(project).sendNotification(
"TypeScript option for Vue",
"You have TypeScript disabled in your shadcn/ui config. This feature is not supported yet. Please install/update your components with the CLI for now.",
NotificationType.WARNING
)
if (!jsVueNotified) {
NotificationManager(project).sendNotification(
"TypeScript option for Vue",
"You have TypeScript disabled in your shadcn/ui config. This feature is not supported yet. Please install/update your components with the CLI for now.",
NotificationType.WARNING
)
jsVueNotified = true
}
// TODO: detype Vue file
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.UpdateInBackground
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.DumbAwareAction

Expand All @@ -13,7 +14,7 @@ import com.intellij.openapi.project.DumbAwareAction
* #com.github.warningimhack3r.intellijshadcnplugin.errorreport.ErrorThrowingAction
* ```
*/
class ErrorThrowingAction : DumbAwareAction() {
class ErrorThrowingAction : DumbAwareAction(), UpdateInBackground {
companion object {
private val LOG = logger<ErrorThrowingAction>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import java.nio.charset.StandardCharsets
// https://github.com/SonarSource/sonarlint-intellij/blob/master/src/main/java/org/sonarlint/intellij/errorsubmitter/BlameSonarSource.java
class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
companion object {
private const val REPO_URL = "https://github.com/WarningImHack3r/intellij-shadcn-plugin"
private const val MAX_URL_LENGTH = 2083
private const val BUG_LOGS_KEY = "bug-logs"
private const val TRIMMED_STACKTRACE_MARKER = "\n\n<TRIMMED STACKTRACE>"
private const val WORM_UNICODE = "\uD83D\uDC1B"
private const val UNICODE_WORM = "\uD83D\uDC1B"
}

override fun getReportActionText() = "$WORM_UNICODE Open an Issue on GitHub"
override fun getReportActionText() = "$UNICODE_WORM Open an Issue on GitHub"

override fun submit(
events: Array<out IdeaLoggingEvent>,
Expand All @@ -37,6 +38,7 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
consumer: Consumer<in SubmittedReportInfo>
): Boolean {
return try {
// Base data
val event = if (events.isNotEmpty()) events.first() else null

val stackTrace = event?.throwableText ?: ""
Expand All @@ -48,13 +50,32 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
DataManager.getInstance().getDataContext(parentComponent)
) ?: getLastFocusedOrOpenedProject()

BrowserUtil.browse(
buildAbbreviatedUrl(mapOf(
// Computed data
var causedByLastIndex = -1
val splitStackTrace = stackTrace.split("\n")
splitStackTrace.reversed().forEachIndexed { index, s ->
if (s.lowercase().startsWith("caused by")) {
causedByLastIndex = splitStackTrace.size - index
return@forEachIndexed
}
}

// Build URL and content
BrowserUtil.browse(buildAbbreviatedUrl(
mapOf(
"title" to "[crash] $simpleErrorMessage",
"bug-explanation" to (additionalInfo ?: ""),
BUG_LOGS_KEY to stackTrace.split("\n").filter {
!it.trim().startsWith("at java.desktop/")
&& !it.trim().startsWith("at java.base/")
BUG_LOGS_KEY to splitStackTrace.filterIndexed { index, s ->
if (index == 0) return@filterIndexed true
val line = s.trim()
if (causedByLastIndex > 0 && line.startsWith("at ") && index < causedByLastIndex) {
return@filterIndexed false
}
!line.startsWith("at java.desktop/")
&& !line.startsWith("at java.base/")
&& !line.startsWith("at kotlin.")
&& !line.startsWith("at kotlinx.")
&& !line.startsWith("at com.intellij.")
}.joinToString("\n"),
/*"device-os" to with(System.getProperty("os.name").lowercase()) {
when { // Windows, macOS or Linux
Expand All @@ -63,9 +84,9 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
else -> "Linux"
}
},*/ // currently cannot be set (https://github.com/orgs/community/discussions/44983)
"additional-device-info" to getDefaultHelpBlock(project)
).filterValues { it.isNotEmpty() })
)
"additional-device-info" to getPlatformAndPluginsInfo(project)
).filterValues { it.isNotEmpty() }
))
consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
true
} catch (e: Exception) {
Expand All @@ -74,54 +95,82 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
}
}

/**
* Build the URL for the GitHub issue from the given fields, abbreviating the URL if necessary
* to fit within the maximum URL length.
*
* @param fields the fields to include in the URL.
* @return the URL for the GitHub issue.
*/
private fun buildAbbreviatedUrl(fields: Map<String, String>): URI {
val url = buildUrl(fields)
return URI(if (url.length > MAX_URL_LENGTH) {
val newMap = fields.toMutableMap()
newMap[BUG_LOGS_KEY]?.let { fullLog ->
val logLessUrlLength = buildUrl(fields.mapValues { (key, value) ->
if (key == BUG_LOGS_KEY) "" else value
}).length
val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length
newMap[BUG_LOGS_KEY] = fullLog.take(
(MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length)
).run {
if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) {
"${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER"
} else this
return URI(
if (url.length > MAX_URL_LENGTH) {
val newMap = fields.toMutableMap()
newMap[BUG_LOGS_KEY]?.let { fullLog ->
val logLessUrlLength = buildUrl(fields.mapValues { (key, value) ->
if (key == BUG_LOGS_KEY) "" else value
}).length
val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length
newMap[BUG_LOGS_KEY] = fullLog.take(
(MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length)
).run {
if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) {
"${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER"
} else this
}
}
}
val shorterLogUrl = buildUrl(newMap)
if (shorterLogUrl.length > MAX_URL_LENGTH) {
buildUrl(fields.filter { (key, _) ->
key == "title" || key == "additional-device-info"
})
} else shorterLogUrl
} else url
val shorterLogUrl = buildUrl(newMap)
if (shorterLogUrl.length > MAX_URL_LENGTH) {
buildUrl(fields.filter { (key, _) ->
key == "title" || key == "additional-device-info"
})
} else shorterLogUrl
} else url
)
}

/**
* Build the URL for the GitHub issue from the given fields.
*
* @param fields the fields to include in the URL.
* @return the URL for the GitHub issue.
*/
private fun buildUrl(fields: Map<String, String>) = buildString {
append("https://github.com/WarningImHack3r/intellij-shadcn-plugin/issues/new?labels=bug&template=bug_report.yml")
append("$REPO_URL/issues/new?labels=bug&template=bug_report.yml")
fields.forEach { (key, value) ->
append("&$key=${URLEncoder.encode(value, StandardCharsets.UTF_8)}")
}
}

private fun getDefaultHelpBlock(project: Project): String {
/**
* Get the platform and plugins information for the given project.
* Used in the "Additional platform info" section of the GitHub issue.
*
* @param project the [Project][com.intellij.openapi.project.Project] to get the platform and plugins information from.
* @return the platform and plugins information for the given project.
*/
private fun getPlatformAndPluginsInfo(project: Project): String {
return CompositeGeneralTroubleInfoCollector().collectInfo(project).run {
val trimmedAndCleaned = split("\n".toRegex()).filter { trim().isNotEmpty() }
// Build, JRE, JVM, OS
trimmedAndCleaned
.dropWhile { s -> s == "=== About ===" }
.takeWhile { s -> s != "=== System ===" }
.filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") }
.joinToString("\n") + "\n" +
// Plugins
buildString {
append(
trimmedAndCleaned
.dropWhile { s -> s == "=== About ===" }
.takeWhile { s -> s != "=== System ===" }
.filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") }
.joinToString("\n")
)
append("\n")
// Plugins
append(
trimmedAndCleaned
.dropWhile { s -> s != "=== Plugins ===" }
.takeWhile { s -> s.isNotBlank() && s.isNotEmpty() }
.joinToString("\n")
)
}
}
}

Expand All @@ -132,12 +181,10 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
* @return the [Project][com.intellij.openapi.project.Project] that was last in focus or open.
*/
private fun getLastFocusedOrOpenedProject(): Project {
val project = IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project
if (project == null) {
return IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: run {
val projectManager = ProjectManager.getInstance()
val openProjects = projectManager.openProjects
return if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject
if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject
}
return project
}
}

0 comments on commit e7ca900

Please sign in to comment.