Skip to content

Commit c0493f9

Browse files
Set expiry for dismissed notifications (#5186)
* dismiss notification expiry * test * fix tests * redundant cleanExpired function calls * converter * detekt * remove whitespace from conflict * detekt * remove attribute tags, unnecessary clear * replace attribute tags * change properties to var for serialization * simplify getState * detekt * reduntant test
1 parent 291be07 commit c0493f9

File tree

2 files changed

+112
-11
lines changed

2 files changed

+112
-11
lines changed

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationStateUtils.kt

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,54 @@ import com.intellij.openapi.components.State
99
import com.intellij.openapi.components.Storage
1010
import com.intellij.openapi.components.service
1111
import software.aws.toolkits.core.utils.ETagProvider
12+
import java.time.Duration
13+
import java.time.Instant
14+
15+
data class DismissedNotification(
16+
var id: String = "",
17+
var dismissedAt: String = Instant.now().toEpochMilli().toString(),
18+
)
19+
20+
data class NotificationDismissalConfiguration(
21+
var dismissedNotifications: MutableSet<DismissedNotification> = mutableSetOf(),
22+
)
1223

1324
@Service
1425
@State(name = "notificationDismissals", storages = [Storage("aws.xml")])
1526
class NotificationDismissalState : PersistentStateComponent<NotificationDismissalConfiguration> {
16-
private val state = NotificationDismissalConfiguration()
27+
private var state = NotificationDismissalConfiguration()
28+
private val retentionPeriod = Duration.ofDays(60) // 2 months
1729

1830
override fun getState(): NotificationDismissalConfiguration = state
1931

2032
override fun loadState(state: NotificationDismissalConfiguration) {
21-
this.state.dismissedNotificationIds.clear()
22-
this.state.dismissedNotificationIds.addAll(state.dismissedNotificationIds)
33+
this.state = state
34+
cleanExpiredNotifications()
2335
}
2436

2537
fun isDismissed(notificationId: String): Boolean =
26-
state.dismissedNotificationIds.contains(notificationId)
38+
state.dismissedNotifications.any { it.id == notificationId }
2739

2840
fun dismissNotification(notificationId: String) {
29-
state.dismissedNotificationIds.add(notificationId)
41+
state.dismissedNotifications.add(
42+
DismissedNotification(
43+
id = notificationId
44+
)
45+
)
46+
}
47+
48+
private fun cleanExpiredNotifications() {
49+
val now = Instant.now()
50+
state.dismissedNotifications.removeAll { notification ->
51+
Duration.between(Instant.ofEpochMilli(notification.dismissedAt.toLong()), now) > retentionPeriod
52+
}
3053
}
3154

3255
companion object {
33-
fun getInstance(): NotificationDismissalState =
34-
service()
56+
fun getInstance(): NotificationDismissalState = service()
3557
}
3658
}
3759

38-
data class NotificationDismissalConfiguration(
39-
var dismissedNotificationIds: MutableSet<String> = mutableSetOf(),
40-
)
41-
4260
@Service
4361
@State(name = "notificationEtag", storages = [Storage("aws.xml")])
4462
class NotificationEtagState : PersistentStateComponent<NotificationEtagConfiguration>, ETagProvider {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.core.notifications
5+
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.Assertions.assertFalse
8+
import org.junit.jupiter.api.Assertions.assertTrue
9+
import org.junit.jupiter.api.BeforeEach
10+
import org.junit.jupiter.api.Test
11+
import java.time.Instant
12+
import java.time.temporal.ChronoUnit
13+
14+
class NotificationDismissalStateTest {
15+
private lateinit var state: NotificationDismissalState
16+
17+
@BeforeEach
18+
fun setUp() {
19+
state = NotificationDismissalState()
20+
}
21+
22+
@Test
23+
fun `notifications less than 2 months old are not removed`() {
24+
val recentNotification = DismissedNotification(
25+
id = "recent-notification",
26+
dismissedAt = Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli().toString()
27+
)
28+
29+
state.loadState(NotificationDismissalConfiguration(mutableSetOf(recentNotification)))
30+
31+
val persistedState = state.getState()
32+
33+
assertEquals(1, persistedState.dismissedNotifications.size)
34+
assertTrue(persistedState.dismissedNotifications.any { it.id == "recent-notification" })
35+
assertTrue(state.isDismissed("recent-notification"))
36+
}
37+
38+
@Test
39+
fun `notifications older than 2 months are removed`() {
40+
val oldNotification = DismissedNotification(
41+
id = "old-notification",
42+
dismissedAt = Instant.now().minus(61, ChronoUnit.DAYS).toEpochMilli().toString()
43+
)
44+
45+
state.loadState(NotificationDismissalConfiguration(mutableSetOf(oldNotification)))
46+
47+
val persistedState = state.getState()
48+
49+
assertEquals(0, persistedState.dismissedNotifications.size)
50+
assertFalse(state.isDismissed("old-notification"))
51+
}
52+
53+
@Test
54+
fun `mixed age notifications are handled correctly`() {
55+
val recentNotification = DismissedNotification(
56+
id = "recent-notification",
57+
dismissedAt = Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli().toString()
58+
)
59+
val oldNotification = DismissedNotification(
60+
id = "old-notification",
61+
dismissedAt = Instant.now().minus(61, ChronoUnit.DAYS).toEpochMilli().toString()
62+
)
63+
64+
state.loadState(
65+
NotificationDismissalConfiguration(
66+
mutableSetOf(recentNotification, oldNotification)
67+
)
68+
)
69+
70+
val persistedState = state.getState()
71+
72+
assertEquals(1, persistedState.dismissedNotifications.size)
73+
assertTrue(state.isDismissed("recent-notification"))
74+
assertFalse(state.isDismissed("old-notification"))
75+
}
76+
77+
@Test
78+
fun `dismissing new notification retains it`() {
79+
state.dismissNotification("new-notification")
80+
81+
assertTrue(state.isDismissed("new-notification"))
82+
}
83+
}

0 commit comments

Comments
 (0)