Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ class DocscribeAnnotator : ExternalAnnotator<AnnotatorFileInfo, DocscribeOutput>
project: Project,
projectDir: String,
) {
val now = System.currentTimeMillis()
if (!shouldShowGemBalloon(lastGemBalloonShown[projectDir], now)) return
lastGemBalloonShown[projectDir] = now
try {
val group =
com.intellij.notification.NotificationGroupManager
Expand Down Expand Up @@ -524,6 +527,31 @@ class DocscribeAnnotator : ExternalAnnotator<AnnotatorFileInfo, DocscribeOutput>
companion object {
private const val MAX_STDERR_PREVIEW = 200

/**
* Minimum interval between "gem not installed" balloons per project directory.
* Prevents spamming the user on every opened file when the gem is missing.
*/
const val BALLOON_THROTTLE_MS = 15 * 60 * 1000L

/**
* Last shown timestamp of the "gem not installed" balloon per project directory.
*/
@VisibleForTesting
internal val lastGemBalloonShown = ConcurrentHashMap<String, Long>()

/**
* Whether the "gem not installed" balloon may be shown now.
*
* @param lastShownMs Previous show timestamp, or `null` if never shown.
* @param nowMs Current time in milliseconds.
* @return `true` on first show or when [BALLOON_THROTTLE_MS] elapsed.
*/
@JvmStatic
fun shouldShowGemBalloon(
lastShownMs: Long?,
nowMs: Long = System.currentTimeMillis(),
): Boolean = lastShownMs == null || nowMs - lastShownMs >= BALLOON_THROTTLE_MS

/**
* Generation counter per file path.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.florexlabs.docscribe.annotator

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class DocscribeAnnotatorBalloonThrottleTest {
@Test
fun `throttle interval is 15 minutes`() {
assertEquals(15 * 60 * 1000L, DocscribeAnnotator.BALLOON_THROTTLE_MS)
}

@Test
fun `first show is allowed`() {
assertTrue(DocscribeAnnotator.shouldShowGemBalloon(null, 1_000L))
}

@Test
fun `repeat within window is suppressed`() {
val now = 1_000_000L
assertFalse(DocscribeAnnotator.shouldShowGemBalloon(now, now))
assertFalse(DocscribeAnnotator.shouldShowGemBalloon(now - 1_000L, now))
assertFalse(DocscribeAnnotator.shouldShowGemBalloon(now - DocscribeAnnotator.BALLOON_THROTTLE_MS + 1, now))
}

@Test
fun `show allowed exactly at boundary`() {
val now = 1_000_000L
assertTrue(DocscribeAnnotator.shouldShowGemBalloon(now - DocscribeAnnotator.BALLOON_THROTTLE_MS, now))
}

@Test
fun `show allowed after window elapsed`() {
val now = 1_000_000L
assertTrue(DocscribeAnnotator.shouldShowGemBalloon(now - DocscribeAnnotator.BALLOON_THROTTLE_MS - 1, now))
assertTrue(DocscribeAnnotator.shouldShowGemBalloon(0L, now))
}
}
Loading